abstract_feature_branch 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +10 -7
- data/VERSION +1 -1
- data/abstract_feature_branch.gemspec +6 -40
- data/lib/abstract_feature_branch/configuration.rb +8 -2
- data/lib/abstract_feature_branch/redis/connection_pool_to_redis_adapter.rb +34 -0
- data/lib/abstract_feature_branch.rb +3 -1
- data/lib/generators/templates/config/initializers/abstract_feature_branch.rb +4 -1
- metadata +5 -39
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -30
- data/RELEASE_NOTES.md +0 -55
- data/TODO.md +0 -3
- data/config/features/admin.local.yml +0 -15
- data/config/features/admin.yml +0 -17
- data/config/features/internal/wiki.local.yml +0 -15
- data/config/features/internal/wiki.yml +0 -17
- data/config/features/public.local.yml +0 -15
- data/config/features/public.yml +0 -17
- data/img/BigAstronaut-Logo.png +0 -0
- data/img/EarlyShares-Logo.svg +0 -22
- data/img/Factor75-Logo.svg +0 -54
- data/ruby187.Gemfile +0 -13
- data/spec/abstract_feature_branch/file_beautifier_spec.rb +0 -384
- data/spec/ext/feature_branch__feature_branch_per_user_spec.rb +0 -122
- data/spec/ext/feature_branch__feature_branch_spec.rb +0 -148
- data/spec/ext/feature_branch__feature_enabled_spec.rb +0 -274
- data/spec/fixtures/application_development_config/config/features.reference.yml +0 -21
- data/spec/fixtures/application_no_config/no_config +0 -1
- data/spec/fixtures/application_rails_config/config/features.local.yml +0 -16
- data/spec/fixtures/application_rails_config/config/features.yml +0 -20
- data/spec/fixtures/application_ugly_config_reference/config/another_application_configuration.yml +0 -31
- data/spec/fixtures/application_ugly_config_reference/config/database.yml +0 -17
- data/spec/fixtures/application_ugly_config_reference/config/features/admin.local.yml +0 -44
- data/spec/fixtures/application_ugly_config_reference/config/features/admin.yml +0 -44
- data/spec/fixtures/application_ugly_config_reference/config/features/empty.local.yml +0 -0
- data/spec/fixtures/application_ugly_config_reference/config/features/feature_empty_config.local.yml +0 -13
- data/spec/fixtures/application_ugly_config_reference/config/features/including_comments.local.yml +0 -52
- data/spec/fixtures/application_ugly_config_reference/config/features/internal/wiki.local.yml +0 -44
- data/spec/fixtures/application_ugly_config_reference/config/features/internal/wiki.yml +0 -44
- data/spec/fixtures/application_ugly_config_reference/config/features/public.local.yml +0 -44
- data/spec/fixtures/application_ugly_config_reference/config/features/public.yml +0 -44
- data/spec/fixtures/application_ugly_config_reference/config/features.local.yml +0 -44
- data/spec/fixtures/application_ugly_config_reference/config/features.yml +0 -49
data/ruby187.Gemfile
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
source 'http://rubygems.org'
|
2
|
-
|
3
|
-
gem 'deep_merge', '1.0.0', :require => false #avoid loading to use only if Rails is unavailable
|
4
|
-
gem 'redis', '~> 3.0.0', :require => false
|
5
|
-
|
6
|
-
group :development do
|
7
|
-
gem 'jeweler', '1.8.8'
|
8
|
-
gem 'rspec', '2.14.1'
|
9
|
-
|
10
|
-
#Ruby 1.8.7 compatible versions
|
11
|
-
gem "nokogiri", "~> 1.5.0"
|
12
|
-
gem "highline", "~> 1.6.21"
|
13
|
-
end
|
@@ -1,384 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe AbstractFeatureBranch::FileBeautifier do
|
4
|
-
describe '#process' do
|
5
|
-
before do
|
6
|
-
@ugly_config_application_root = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config'))
|
7
|
-
@ugly_config_application_reference_root = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config_reference'))
|
8
|
-
FileUtils.rm_rf( @ugly_config_application_root)
|
9
|
-
FileUtils.cp_r(@ugly_config_application_reference_root, @ugly_config_application_root)
|
10
|
-
end
|
11
|
-
after do
|
12
|
-
FileUtils.rm_rf( @ugly_config_application_root)
|
13
|
-
end
|
14
|
-
|
15
|
-
context "a file is specified" do
|
16
|
-
it 'gets rid of extra empty lines' do
|
17
|
-
feature_file_path = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config', 'config', 'features.yml'))
|
18
|
-
AbstractFeatureBranch::FileBeautifier.process(feature_file_path)
|
19
|
-
File.open(feature_file_path, 'r') do |file|
|
20
|
-
file.readlines.join.should == <<-EXPECTED_FILE_CONTENT
|
21
|
-
defaults: &defaults
|
22
|
-
FEATURE1: true
|
23
|
-
Feature2: true
|
24
|
-
feature3: false
|
25
|
-
feature4: true
|
26
|
-
feature4a: true
|
27
|
-
|
28
|
-
development:
|
29
|
-
<<: *defaults
|
30
|
-
FEATURE1: true
|
31
|
-
Feature2: true
|
32
|
-
feature3: false
|
33
|
-
feature4: true
|
34
|
-
feature4a: true
|
35
|
-
|
36
|
-
test:
|
37
|
-
<<: *defaults
|
38
|
-
FEATURE1: true
|
39
|
-
Feature2: true
|
40
|
-
feature3: false
|
41
|
-
feature4: true
|
42
|
-
feature4a: true
|
43
|
-
|
44
|
-
staging:
|
45
|
-
<<: *defaults
|
46
|
-
FEATURE1: true
|
47
|
-
Feature2: true
|
48
|
-
feature3: false
|
49
|
-
feature4: true
|
50
|
-
feature4a: true
|
51
|
-
|
52
|
-
production:
|
53
|
-
<<: *defaults
|
54
|
-
FEATURE1: true
|
55
|
-
Feature2: true
|
56
|
-
feature3: false
|
57
|
-
feature4: true
|
58
|
-
feature4a: true
|
59
|
-
|
60
|
-
EXPECTED_FILE_CONTENT
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'sorts features by name under each section (e.g. environment)' do
|
65
|
-
local_feature_file_path = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config', 'config', 'features.local.yml'))
|
66
|
-
AbstractFeatureBranch::FileBeautifier.process(local_feature_file_path)
|
67
|
-
File.open(local_feature_file_path, 'r') do |file|
|
68
|
-
file.readlines.join.should == <<-EXPECTED_FILE_CONTENT
|
69
|
-
defaults: &defaults
|
70
|
-
FEATURE1: true
|
71
|
-
Feature2: true
|
72
|
-
feature3: false
|
73
|
-
feature4: true
|
74
|
-
feature4a: true
|
75
|
-
|
76
|
-
development:
|
77
|
-
<<: *defaults
|
78
|
-
FEATURE1: true
|
79
|
-
Feature2: true
|
80
|
-
feature3: false
|
81
|
-
feature4: true
|
82
|
-
feature4a: true
|
83
|
-
|
84
|
-
test:
|
85
|
-
<<: *defaults
|
86
|
-
FEATURE1: true
|
87
|
-
Feature2: true
|
88
|
-
feature3: false
|
89
|
-
feature4: true
|
90
|
-
feature4a: true
|
91
|
-
|
92
|
-
staging:
|
93
|
-
<<: *defaults
|
94
|
-
FEATURE1: true
|
95
|
-
Feature2: true
|
96
|
-
feature3: false
|
97
|
-
feature4: true
|
98
|
-
feature4a: true
|
99
|
-
|
100
|
-
production:
|
101
|
-
<<: *defaults
|
102
|
-
FEATURE1: true
|
103
|
-
Feature2: true
|
104
|
-
feature3: false
|
105
|
-
feature4: true
|
106
|
-
feature4a: true
|
107
|
-
|
108
|
-
EXPECTED_FILE_CONTENT
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
it 'handles comments by ignoring comments on top and deleting comments in the middle' do
|
113
|
-
feature_file_path = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config', 'config', 'features', 'including_comments.local.yml'))
|
114
|
-
AbstractFeatureBranch::FileBeautifier.process(feature_file_path)
|
115
|
-
File.open(feature_file_path, 'r') do |file|
|
116
|
-
file.readlines.join.should == <<-EXPECTED_FILE_CONTENT
|
117
|
-
# This file allows you to override any feature configuration locally without it being committed to git
|
118
|
-
# It is recommended to use this file only for temporary overrides. Once done, make final change in main .yml
|
119
|
-
defaults: &defaults
|
120
|
-
FEATURE1: true
|
121
|
-
Feature2: true
|
122
|
-
feature3: false
|
123
|
-
feature4: true
|
124
|
-
feature4a: true
|
125
|
-
|
126
|
-
development:
|
127
|
-
<<: *defaults
|
128
|
-
FEATURE1: true
|
129
|
-
Feature2: true
|
130
|
-
feature3: false
|
131
|
-
feature4: true
|
132
|
-
feature4a: true
|
133
|
-
|
134
|
-
test:
|
135
|
-
<<: *defaults
|
136
|
-
FEATURE1: true
|
137
|
-
Feature2: true
|
138
|
-
feature3: false
|
139
|
-
feature4: true
|
140
|
-
feature4a: true
|
141
|
-
|
142
|
-
staging:
|
143
|
-
<<: *defaults
|
144
|
-
FEATURE1: true
|
145
|
-
Feature2: true
|
146
|
-
feature3: false
|
147
|
-
feature4: true
|
148
|
-
feature4a: true
|
149
|
-
|
150
|
-
production:
|
151
|
-
<<: *defaults
|
152
|
-
FEATURE1: true
|
153
|
-
Feature2: true
|
154
|
-
feature3: false
|
155
|
-
feature4: true
|
156
|
-
feature4a: true
|
157
|
-
|
158
|
-
EXPECTED_FILE_CONTENT
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
it 'processes a feature empty config file' do
|
163
|
-
feature_file_path = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config', 'config', 'features', 'feature_empty_config.local.yml'))
|
164
|
-
AbstractFeatureBranch::FileBeautifier.process(feature_file_path)
|
165
|
-
File.open(feature_file_path, 'r') do |file|
|
166
|
-
file.readlines.join.should == <<-EXPECTED_FILE_CONTENT
|
167
|
-
defaults: &defaults
|
168
|
-
|
169
|
-
|
170
|
-
development:
|
171
|
-
<<: *defaults
|
172
|
-
|
173
|
-
test:
|
174
|
-
<<: *defaults
|
175
|
-
|
176
|
-
staging:
|
177
|
-
<<: *defaults
|
178
|
-
|
179
|
-
production:
|
180
|
-
<<: *defaults
|
181
|
-
|
182
|
-
EXPECTED_FILE_CONTENT
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
it 'processes an empty file without change or exceptions' do
|
187
|
-
feature_file_path = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config', 'config', 'features', 'empty.local.yml'))
|
188
|
-
AbstractFeatureBranch::FileBeautifier.process(feature_file_path)
|
189
|
-
File.open(feature_file_path, 'r') do |file|
|
190
|
-
file.readlines.join.should be_empty
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
end
|
195
|
-
|
196
|
-
context "a directory is specified" do
|
197
|
-
it 'beautifies all YAML files under specified directory recursively' do
|
198
|
-
feature_directory_path = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config', 'config', 'features'))
|
199
|
-
AbstractFeatureBranch::FileBeautifier.process(feature_directory_path)
|
200
|
-
|
201
|
-
['public.yml', 'public.local.yml', 'admin.yml', 'admin.local.yml', 'internal/wiki.yml', 'internal/wiki.local.yml'].each do |file_path_suffix|
|
202
|
-
file_path = File.join(feature_directory_path, file_path_suffix)
|
203
|
-
File.open(file_path, 'r') do |file|
|
204
|
-
file.readlines.join.should == <<-EXPECTED_FILE_CONTENT
|
205
|
-
defaults: &defaults
|
206
|
-
FEATURE1: true
|
207
|
-
Feature2: true
|
208
|
-
feature3: false
|
209
|
-
feature4: true
|
210
|
-
feature4a: true
|
211
|
-
|
212
|
-
development:
|
213
|
-
<<: *defaults
|
214
|
-
FEATURE1: true
|
215
|
-
Feature2: true
|
216
|
-
feature3: false
|
217
|
-
feature4: true
|
218
|
-
feature4a: true
|
219
|
-
|
220
|
-
test:
|
221
|
-
<<: *defaults
|
222
|
-
FEATURE1: true
|
223
|
-
Feature2: true
|
224
|
-
feature3: false
|
225
|
-
feature4: true
|
226
|
-
feature4a: true
|
227
|
-
|
228
|
-
staging:
|
229
|
-
<<: *defaults
|
230
|
-
FEATURE1: true
|
231
|
-
Feature2: true
|
232
|
-
feature3: false
|
233
|
-
feature4: true
|
234
|
-
feature4a: true
|
235
|
-
|
236
|
-
production:
|
237
|
-
<<: *defaults
|
238
|
-
FEATURE1: true
|
239
|
-
Feature2: true
|
240
|
-
feature3: false
|
241
|
-
feature4: true
|
242
|
-
feature4a: true
|
243
|
-
|
244
|
-
EXPECTED_FILE_CONTENT
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
context "no file or directory is specified (process all feature files)" do
|
250
|
-
after do
|
251
|
-
AbstractFeatureBranch.initialize_application_root
|
252
|
-
AbstractFeatureBranch.load_application_features
|
253
|
-
end
|
254
|
-
it 'beautifies all feature files in the application' do
|
255
|
-
AbstractFeatureBranch.application_root = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config'))
|
256
|
-
AbstractFeatureBranch.load_application_features
|
257
|
-
AbstractFeatureBranch::FileBeautifier.process
|
258
|
-
|
259
|
-
[
|
260
|
-
'features.yml',
|
261
|
-
'features.local.yml',
|
262
|
-
'features/public.yml',
|
263
|
-
'features/public.local.yml',
|
264
|
-
'features/admin.yml',
|
265
|
-
'features/admin.local.yml',
|
266
|
-
'features/internal/wiki.yml',
|
267
|
-
'features/internal/wiki.local.yml'
|
268
|
-
].each do |file_path_suffix|
|
269
|
-
file_path = File.join(AbstractFeatureBranch.application_root, 'config', file_path_suffix)
|
270
|
-
File.open(file_path, 'r') do |file|
|
271
|
-
file.readlines.join.should == <<-EXPECTED_FILE_CONTENT
|
272
|
-
defaults: &defaults
|
273
|
-
FEATURE1: true
|
274
|
-
Feature2: true
|
275
|
-
feature3: false
|
276
|
-
feature4: true
|
277
|
-
feature4a: true
|
278
|
-
|
279
|
-
development:
|
280
|
-
<<: *defaults
|
281
|
-
FEATURE1: true
|
282
|
-
Feature2: true
|
283
|
-
feature3: false
|
284
|
-
feature4: true
|
285
|
-
feature4a: true
|
286
|
-
|
287
|
-
test:
|
288
|
-
<<: *defaults
|
289
|
-
FEATURE1: true
|
290
|
-
Feature2: true
|
291
|
-
feature3: false
|
292
|
-
feature4: true
|
293
|
-
feature4a: true
|
294
|
-
|
295
|
-
staging:
|
296
|
-
<<: *defaults
|
297
|
-
FEATURE1: true
|
298
|
-
Feature2: true
|
299
|
-
feature3: false
|
300
|
-
feature4: true
|
301
|
-
feature4a: true
|
302
|
-
|
303
|
-
production:
|
304
|
-
<<: *defaults
|
305
|
-
FEATURE1: true
|
306
|
-
Feature2: true
|
307
|
-
feature3: false
|
308
|
-
feature4: true
|
309
|
-
feature4a: true
|
310
|
-
|
311
|
-
EXPECTED_FILE_CONTENT
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
it 'does not beautify non-feature files in the application' do
|
317
|
-
AbstractFeatureBranch.application_root = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_ugly_config'))
|
318
|
-
AbstractFeatureBranch.load_application_features
|
319
|
-
AbstractFeatureBranch::FileBeautifier.process
|
320
|
-
|
321
|
-
file_path = File.join(AbstractFeatureBranch.application_root, 'config', 'another_application_configuration.yml')
|
322
|
-
File.open(file_path, 'r') do |file|
|
323
|
-
file.readlines.join.should == <<-ANOTHER_APPLICATION_CONFIGURATION_CONTENT
|
324
|
-
common: &default_settings
|
325
|
-
license_key: <%= ENV["LICENSE_KEY"] %>
|
326
|
-
app_name: <%= ENV["APP_NAME"] %>
|
327
|
-
monitor_mode: true
|
328
|
-
developer_mode: false
|
329
|
-
log_level: info
|
330
|
-
|
331
|
-
browser_monitoring:
|
332
|
-
auto_instrument: true
|
333
|
-
|
334
|
-
audit_log:
|
335
|
-
enabled: false
|
336
|
-
|
337
|
-
|
338
|
-
development:
|
339
|
-
<<: *default_settings
|
340
|
-
monitor_mode: false
|
341
|
-
developer_mode: true
|
342
|
-
|
343
|
-
test:
|
344
|
-
<<: *default_settings
|
345
|
-
monitor_mode: false
|
346
|
-
|
347
|
-
production:
|
348
|
-
<<: *default_settings
|
349
|
-
monitor_mode: true
|
350
|
-
|
351
|
-
staging:
|
352
|
-
<<: *default_settings
|
353
|
-
monitor_mode: true
|
354
|
-
app_name: <%= ENV["APP_NAME"] %> (Staging)
|
355
|
-
ANOTHER_APPLICATION_CONFIGURATION_CONTENT
|
356
|
-
end
|
357
|
-
|
358
|
-
file_path = File.join(AbstractFeatureBranch.application_root, 'config', 'database.yml')
|
359
|
-
File.open(file_path, 'r') do |file|
|
360
|
-
file.readlines.join.should == <<-DATABASE_CONFIGURATION_CONTENT
|
361
|
-
development:
|
362
|
-
adapter: sqlite3
|
363
|
-
database: db/development.sqlite3
|
364
|
-
pool: 5
|
365
|
-
timeout: 5000
|
366
|
-
|
367
|
-
test:
|
368
|
-
adapter: sqlite3
|
369
|
-
database: db/test.sqlite3
|
370
|
-
pool: 5
|
371
|
-
timeout: 5000
|
372
|
-
|
373
|
-
production:
|
374
|
-
adapter: sqlite3
|
375
|
-
database: db/production.sqlite3
|
376
|
-
pool: 5
|
377
|
-
timeout: 5000
|
378
|
-
DATABASE_CONFIGURATION_CONTENT
|
379
|
-
end
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|
383
|
-
end
|
384
|
-
end
|
@@ -1,122 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe 'feature_branch object extensions' do
|
4
|
-
before do
|
5
|
-
@app_env_backup = AbstractFeatureBranch.application_environment
|
6
|
-
@app_root_backup = AbstractFeatureBranch.application_root
|
7
|
-
AbstractFeatureBranch.logger.warn 'Environment variable ABSTRACT_FEATURE_BRANCH_FEATURE1 already set, potentially conflicting with another test' if ENV.keys.include?('ABSTRACT_FEATURE_BRANCH_FEATURE1')
|
8
|
-
AbstractFeatureBranch.logger.warn 'Environment variable Abstract_Feature_Branch_Feature2 already set, potentially conflicting with another test' if ENV.keys.include?('Abstract_Feature_Branch_Feature2')
|
9
|
-
AbstractFeatureBranch.logger.warn 'Environment variable abstract_feature_branch_feature3 already set, potentially conflicting with another test' if ENV.keys.include?('abstract_feature_branch_feature3')
|
10
|
-
begin
|
11
|
-
AbstractFeatureBranch.user_features_storage.flushall
|
12
|
-
rescue => e
|
13
|
-
#noop
|
14
|
-
end
|
15
|
-
end
|
16
|
-
after do
|
17
|
-
ENV.delete('ABSTRACT_FEATURE_BRANCH_FEATURE1')
|
18
|
-
ENV.delete('Abstract_Feature_Branch_Feature2')
|
19
|
-
ENV.delete('abstract_feature_branch_feature3')
|
20
|
-
AbstractFeatureBranch.application_root = @app_root_backup
|
21
|
-
AbstractFeatureBranch.application_environment = @app_env_backup
|
22
|
-
AbstractFeatureBranch.unload_application_features
|
23
|
-
begin
|
24
|
-
AbstractFeatureBranch.user_features_storage.flushall
|
25
|
-
rescue => e
|
26
|
-
#noop
|
27
|
-
end
|
28
|
-
AbstractFeatureBranch.user_features_storage.keys.each do |key|
|
29
|
-
AbstractFeatureBranch.user_features_storage.del(key)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
describe '#feature_branch' do
|
33
|
-
context 'per user' do
|
34
|
-
it 'feature branches correctly after storing feature configuration per user in a separate process (ensuring persistence)' do
|
35
|
-
user_id = 'email1@example.com'
|
36
|
-
ruby_code = <<-RUBY_CODE
|
37
|
-
$:.unshift('.')
|
38
|
-
require 'redis'
|
39
|
-
require 'lib/abstract_feature_branch'
|
40
|
-
AbstractFeatureBranch.initialize_user_features_storage
|
41
|
-
AbstractFeatureBranch.toggle_features_for_user('#{user_id}', :feature1 => false, :feature3 => true, :feature6 => true, :feature7 => false)
|
42
|
-
RUBY_CODE
|
43
|
-
system "ruby -e \"#{ruby_code}\""
|
44
|
-
features_enabled = []
|
45
|
-
feature_branch :feature1, user_id do
|
46
|
-
features_enabled << :feature1
|
47
|
-
end
|
48
|
-
feature_branch :feature3, user_id do
|
49
|
-
features_enabled << :feature3
|
50
|
-
end
|
51
|
-
feature_branch :feature6, user_id do
|
52
|
-
features_enabled << :feature6
|
53
|
-
end
|
54
|
-
feature_branch :feature6, 'otheruser@example.com' do
|
55
|
-
features_enabled << :feature6_otheruser
|
56
|
-
end
|
57
|
-
feature_branch :feature6 do
|
58
|
-
features_enabled << :feature6_nouserspecified
|
59
|
-
end
|
60
|
-
feature_branch :feature7, user_id do
|
61
|
-
features_enabled << :feature7
|
62
|
-
end
|
63
|
-
features_enabled.should include(:feature1) #remains like features.yml
|
64
|
-
features_enabled.should_not include(:feature3) #remains like features.yml
|
65
|
-
features_enabled.should include(:feature6) #per user honored as true
|
66
|
-
features_enabled.should_not include(:feature6_otheruser) #per user honored as false
|
67
|
-
features_enabled.should_not include(:feature6_nouserspecified) #per user requires user id or it returns false
|
68
|
-
features_enabled.should_not include(:feature7) #per user honored as false
|
69
|
-
end
|
70
|
-
it 'update feature branching (disabling some features) after having stored feature configuration per user in a separate process (ensuring persistence)' do
|
71
|
-
user_id = 'email1@example.com'
|
72
|
-
ruby_code = <<-RUBY_CODE
|
73
|
-
$:.unshift('.')
|
74
|
-
require 'redis'
|
75
|
-
require 'lib/abstract_feature_branch'
|
76
|
-
AbstractFeatureBranch.initialize_user_features_storage
|
77
|
-
AbstractFeatureBranch.toggle_features_for_user('#{user_id}', :feature6 => true, :feature7 => false)
|
78
|
-
AbstractFeatureBranch.toggle_features_for_user('#{user_id}', :feature6 => false, :feature7 => true)
|
79
|
-
RUBY_CODE
|
80
|
-
system "ruby -e \"#{ruby_code}\""
|
81
|
-
features_enabled = []
|
82
|
-
feature_branch :feature6, user_id do
|
83
|
-
features_enabled << :feature6
|
84
|
-
end
|
85
|
-
feature_branch :feature7, user_id do
|
86
|
-
features_enabled << :feature7
|
87
|
-
end
|
88
|
-
features_enabled.should_not include(:feature6)
|
89
|
-
features_enabled.should include(:feature7)
|
90
|
-
end
|
91
|
-
|
92
|
-
end
|
93
|
-
end
|
94
|
-
describe 'self#feature_branch' do
|
95
|
-
after do
|
96
|
-
Object.send(:remove_const, :TestObject)
|
97
|
-
end
|
98
|
-
# No need to retest all instance test cases, just a spot check due to implementation reuse
|
99
|
-
it 'feature branches instance level behavior (case-insensitive feature names)' do
|
100
|
-
class TestObject
|
101
|
-
def self.features_enabled
|
102
|
-
@features_enabled ||= []
|
103
|
-
end
|
104
|
-
def self.hit_me
|
105
|
-
feature_branch :feature1 do
|
106
|
-
self.features_enabled << :feature1
|
107
|
-
end
|
108
|
-
feature_branch :feature2 do
|
109
|
-
self.features_enabled << :feature2
|
110
|
-
end
|
111
|
-
feature_branch :feature3 do
|
112
|
-
self.features_enabled << :feature3
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
TestObject.hit_me
|
117
|
-
TestObject.features_enabled.should include(:feature1)
|
118
|
-
TestObject.features_enabled.should include(:feature2)
|
119
|
-
TestObject.features_enabled.should_not include(:feature3)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
@@ -1,148 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe 'feature_branch object extensions' do
|
4
|
-
before do
|
5
|
-
@app_env_backup = AbstractFeatureBranch.application_environment
|
6
|
-
@app_root_backup = AbstractFeatureBranch.application_root
|
7
|
-
AbstractFeatureBranch.logger.warn 'Environment variable ABSTRACT_FEATURE_BRANCH_FEATURE1 already set, potentially conflicting with another test' if ENV.keys.include?('ABSTRACT_FEATURE_BRANCH_FEATURE1')
|
8
|
-
AbstractFeatureBranch.logger.warn 'Environment variable Abstract_Feature_Branch_Feature2 already set, potentially conflicting with another test' if ENV.keys.include?('Abstract_Feature_Branch_Feature2')
|
9
|
-
AbstractFeatureBranch.logger.warn 'Environment variable abstract_feature_branch_feature3 already set, potentially conflicting with another test' if ENV.keys.include?('abstract_feature_branch_feature3')
|
10
|
-
end
|
11
|
-
after do
|
12
|
-
ENV.delete('ABSTRACT_FEATURE_BRANCH_FEATURE1')
|
13
|
-
ENV.delete('Abstract_Feature_Branch_Feature2')
|
14
|
-
ENV.delete('abstract_feature_branch_feature3')
|
15
|
-
AbstractFeatureBranch.application_root = @app_root_backup
|
16
|
-
AbstractFeatureBranch.application_environment = @app_env_backup
|
17
|
-
AbstractFeatureBranch.unload_application_features
|
18
|
-
AbstractFeatureBranch.user_features_storage.keys.each do |key|
|
19
|
-
AbstractFeatureBranch.user_features_storage.del(key)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
describe '#feature_branch' do
|
23
|
-
context 'class level behavior (case-insensitive string or symbol feature names)' do
|
24
|
-
{
|
25
|
-
'Feature1' => true,
|
26
|
-
:FEATURE2 => true,
|
27
|
-
:feature3 => false,
|
28
|
-
:admin_feature1 => true,
|
29
|
-
:admin_feature2 => false,
|
30
|
-
:public_feature1 => true,
|
31
|
-
:public_feature2 => false,
|
32
|
-
:wiki_feature1 => true,
|
33
|
-
:wiki_feature2 => false,
|
34
|
-
}.each do |feature_name, expected_branch_run|
|
35
|
-
it "feature branches correctly for feature #{feature_name} with expected branch run #{expected_branch_run}" do
|
36
|
-
feature_branch_run = false
|
37
|
-
feature_branch feature_name do
|
38
|
-
feature_branch_run = true
|
39
|
-
end
|
40
|
-
feature_branch_run.should == expected_branch_run
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
it 'returns nil and does not execute block for an invalid feature name' do
|
45
|
-
return_value = feature_branch :invalid_feature_that_does_not_exist do
|
46
|
-
fail 'feature branch block must not execute, but did.'
|
47
|
-
end
|
48
|
-
return_value.should be_nil
|
49
|
-
end
|
50
|
-
it 'allows environment variables (case-insensitive booleans) to override configuration file' do
|
51
|
-
ENV['ABSTRACT_FEATURE_BRANCH_FEATURE1'] = 'FALSE'
|
52
|
-
ENV['Abstract_Feature_Branch_Feature2'] = 'False'
|
53
|
-
ENV['abstract_feature_branch_feature3'] = 'true'
|
54
|
-
AbstractFeatureBranch.load_application_features
|
55
|
-
features_enabled = []
|
56
|
-
feature_branch :feature1 do
|
57
|
-
features_enabled << :feature1
|
58
|
-
end
|
59
|
-
feature_branch :feature2 do
|
60
|
-
features_enabled << :feature2
|
61
|
-
end
|
62
|
-
feature_branch :feature3 do
|
63
|
-
features_enabled << :feature3
|
64
|
-
end
|
65
|
-
features_enabled.should_not include(:feature1)
|
66
|
-
features_enabled.should_not include(:feature2)
|
67
|
-
features_enabled.should include(:feature3)
|
68
|
-
end
|
69
|
-
it 'allows redis variables (case-insensitive booleans) to override configuration file' do
|
70
|
-
AbstractFeatureBranch.unload_application_features
|
71
|
-
AbstractFeatureBranch.user_features_storage.hset('abstract_feature_branch', 'FEATURE1', 'FALSE')
|
72
|
-
AbstractFeatureBranch.user_features_storage.hset('abstract_feature_branch', 'Feature2', 'False')
|
73
|
-
AbstractFeatureBranch.user_features_storage.hset('abstract_feature_branch', 'feature3', 'true')
|
74
|
-
AbstractFeatureBranch.load_application_features
|
75
|
-
features_enabled = []
|
76
|
-
feature_branch :feature1 do
|
77
|
-
features_enabled << :feature1
|
78
|
-
end
|
79
|
-
feature_branch :feature2 do
|
80
|
-
features_enabled << :feature2
|
81
|
-
end
|
82
|
-
feature_branch :feature3 do
|
83
|
-
features_enabled << :feature3
|
84
|
-
end
|
85
|
-
features_enabled.should_not include(:feature1)
|
86
|
-
features_enabled.should_not include(:feature2)
|
87
|
-
features_enabled.should include(:feature3)
|
88
|
-
end
|
89
|
-
it 'allows local configuration file to override main configuration file' do
|
90
|
-
features_enabled = []
|
91
|
-
feature_branch :feature4 do
|
92
|
-
features_enabled << :feature4
|
93
|
-
end
|
94
|
-
feature_branch :feature5 do
|
95
|
-
features_enabled << :feature5
|
96
|
-
end
|
97
|
-
feature_branch :admin_feature3 do
|
98
|
-
features_enabled << :admin_feature3
|
99
|
-
end
|
100
|
-
feature_branch :public_feature3 do
|
101
|
-
features_enabled << :public_feature3
|
102
|
-
end
|
103
|
-
feature_branch :wiki_feature3 do
|
104
|
-
features_enabled << :wiki_feature3
|
105
|
-
end
|
106
|
-
features_enabled.should_not include(:feature4)
|
107
|
-
features_enabled.should include(:feature5)
|
108
|
-
features_enabled.should include(:admin_feature3)
|
109
|
-
features_enabled.should include(:public_feature3)
|
110
|
-
features_enabled.should include(:wiki_feature3)
|
111
|
-
end
|
112
|
-
it 'works with an application that has no configuration files' do
|
113
|
-
AbstractFeatureBranch.application_root = File.join(__FILE__, '..', '..', 'fixtures', 'application_no_config')
|
114
|
-
AbstractFeatureBranch.load_application_features
|
115
|
-
feature_branch :feature1 do
|
116
|
-
fail 'feature branch block must not execute, but did.'
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
describe 'self#feature_branch' do
|
121
|
-
after do
|
122
|
-
Object.send(:remove_const, :TestObject)
|
123
|
-
end
|
124
|
-
# No need to retest all instance test cases, just a spot check due to implementation reuse
|
125
|
-
it 'feature branches instance level behavior (case-insensitive feature names)' do
|
126
|
-
class TestObject
|
127
|
-
def self.features_enabled
|
128
|
-
@features_enabled ||= []
|
129
|
-
end
|
130
|
-
def self.hit_me
|
131
|
-
feature_branch :feature1 do
|
132
|
-
self.features_enabled << :feature1
|
133
|
-
end
|
134
|
-
feature_branch :feature2 do
|
135
|
-
self.features_enabled << :feature2
|
136
|
-
end
|
137
|
-
feature_branch :feature3 do
|
138
|
-
self.features_enabled << :feature3
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
TestObject.hit_me
|
143
|
-
TestObject.features_enabled.should include(:feature1)
|
144
|
-
TestObject.features_enabled.should include(:feature2)
|
145
|
-
TestObject.features_enabled.should_not include(:feature3)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|