flip_fab 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +5 -0
  3. data/Gemfile +16 -0
  4. data/Gemfile.lock +92 -0
  5. data/LICENSE.txt +9 -0
  6. data/README.md +168 -0
  7. data/example/rails_app/.gitignore +17 -0
  8. data/example/rails_app/Gemfile +36 -0
  9. data/example/rails_app/Gemfile.lock +178 -0
  10. data/example/rails_app/README.rdoc +28 -0
  11. data/example/rails_app/Rakefile +6 -0
  12. data/example/rails_app/app/assets/images/.keep +0 -0
  13. data/example/rails_app/app/assets/images/justin_beaver.jpg +0 -0
  14. data/example/rails_app/app/assets/images/regular_beaver.jpg +0 -0
  15. data/example/rails_app/app/assets/javascripts/application.js +16 -0
  16. data/example/rails_app/app/assets/javascripts/beavers.js.coffee +3 -0
  17. data/example/rails_app/app/assets/stylesheets/application.css +15 -0
  18. data/example/rails_app/app/assets/stylesheets/beavers.css.scss +3 -0
  19. data/example/rails_app/app/assets/stylesheets/scaffolds.css.scss +69 -0
  20. data/example/rails_app/app/controllers/application_controller.rb +5 -0
  21. data/example/rails_app/app/controllers/beavers_controller.rb +74 -0
  22. data/example/rails_app/app/controllers/concerns/.keep +0 -0
  23. data/example/rails_app/app/helpers/application_helper.rb +2 -0
  24. data/example/rails_app/app/helpers/beavers_helper.rb +2 -0
  25. data/example/rails_app/app/mailers/.keep +0 -0
  26. data/example/rails_app/app/models/.keep +0 -0
  27. data/example/rails_app/app/models/beaver.rb +2 -0
  28. data/example/rails_app/app/models/concerns/.keep +0 -0
  29. data/example/rails_app/app/views/beavers/_form.html.erb +21 -0
  30. data/example/rails_app/app/views/beavers/edit.html.erb +6 -0
  31. data/example/rails_app/app/views/beavers/index.html.erb +25 -0
  32. data/example/rails_app/app/views/beavers/index.json.jbuilder +4 -0
  33. data/example/rails_app/app/views/beavers/new.html.erb +5 -0
  34. data/example/rails_app/app/views/beavers/show.html.erb +9 -0
  35. data/example/rails_app/app/views/beavers/show.json.jbuilder +1 -0
  36. data/example/rails_app/app/views/layouts/application.html.erb +18 -0
  37. data/example/rails_app/bin/bundle +3 -0
  38. data/example/rails_app/bin/rails +4 -0
  39. data/example/rails_app/bin/rake +4 -0
  40. data/example/rails_app/config/application.rb +25 -0
  41. data/example/rails_app/config/boot.rb +4 -0
  42. data/example/rails_app/config/database.yml +22 -0
  43. data/example/rails_app/config/environment.rb +5 -0
  44. data/example/rails_app/config/environments/development.rb +83 -0
  45. data/example/rails_app/config/environments/test.rb +41 -0
  46. data/example/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  47. data/example/rails_app/config/initializers/cookies_serializer.rb +3 -0
  48. data/example/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
  49. data/example/rails_app/config/initializers/flip_fab.rb +1 -0
  50. data/example/rails_app/config/initializers/inflections.rb +16 -0
  51. data/example/rails_app/config/initializers/mime_types.rb +4 -0
  52. data/example/rails_app/config/initializers/session_store.rb +3 -0
  53. data/example/rails_app/config/initializers/wrap_parameters.rb +14 -0
  54. data/example/rails_app/config/locales/en.yml +23 -0
  55. data/example/rails_app/config/rabbit_feed.yml +8 -0
  56. data/example/rails_app/config/routes.rb +58 -0
  57. data/example/rails_app/config/secrets.yml +18 -0
  58. data/example/rails_app/config/unicorn.rb +4 -0
  59. data/example/rails_app/config.ru +4 -0
  60. data/example/rails_app/db/migrate/20140424102400_create_beavers.rb +9 -0
  61. data/example/rails_app/db/schema.rb +22 -0
  62. data/example/rails_app/db/seeds.rb +7 -0
  63. data/example/rails_app/lib/assets/.keep +0 -0
  64. data/example/rails_app/lib/tasks/.keep +0 -0
  65. data/example/rails_app/log/.keep +0 -0
  66. data/example/rails_app/public/404.html +67 -0
  67. data/example/rails_app/public/422.html +67 -0
  68. data/example/rails_app/public/500.html +66 -0
  69. data/example/rails_app/public/favicon.ico +0 -0
  70. data/example/rails_app/public/robots.txt +5 -0
  71. data/example/rails_app/spec/rails_helper.rb +50 -0
  72. data/example/rails_app/spec/spec_helper.rb +8 -0
  73. data/example/rails_app/test/controllers/.keep +0 -0
  74. data/example/rails_app/test/controllers/beavers_controller_test.rb +49 -0
  75. data/example/rails_app/test/fixtures/.keep +0 -0
  76. data/example/rails_app/test/fixtures/beavers.yml +7 -0
  77. data/example/rails_app/test/helpers/.keep +0 -0
  78. data/example/rails_app/test/helpers/beavers_helper_test.rb +4 -0
  79. data/example/rails_app/test/integration/.keep +0 -0
  80. data/example/rails_app/test/mailers/.keep +0 -0
  81. data/example/rails_app/test/models/.keep +0 -0
  82. data/example/rails_app/test/models/beaver_test.rb +7 -0
  83. data/example/rails_app/test/test_helper.rb +13 -0
  84. data/flip_fab.gemspec +20 -0
  85. data/lib/flip_fab/contextual_feature.rb +83 -0
  86. data/lib/flip_fab/cookie_persistence.rb +52 -0
  87. data/lib/flip_fab/feature.rb +24 -0
  88. data/lib/flip_fab/features_by_name.rb +22 -0
  89. data/lib/flip_fab/helper.rb +8 -0
  90. data/lib/flip_fab/persistence.rb +19 -0
  91. data/lib/flip_fab/version.rb +3 -0
  92. data/lib/flip_fab.rb +24 -0
  93. data/spec/lib/flip_fab/contextual_feature_spec.rb +352 -0
  94. data/spec/lib/flip_fab/cookie_persistence.feature +50 -0
  95. data/spec/lib/flip_fab/cookie_persistence_spec.rb +90 -0
  96. data/spec/lib/flip_fab/feature_spec.rb +86 -0
  97. data/spec/lib/flip_fab/features_by_name_spec.rb +34 -0
  98. data/spec/lib/flip_fab/helper.feature +31 -0
  99. data/spec/lib/flip_fab/helper_spec.rb +90 -0
  100. data/spec/lib/flip_fab/persistence_spec.rb +32 -0
  101. data/spec/lib/flip_fab.feature +10 -0
  102. data/spec/lib/flip_fab_spec.rb +47 -0
  103. data/spec/spec_helper.rb +93 -0
  104. data/spec/support/test_app.rb +16 -0
  105. data/spec/support/test_context.rb +10 -0
  106. data/spec/support/test_multiple_persistence.rb +14 -0
  107. data/spec/support/test_persistence.rb +14 -0
  108. data/spec/support/test_rack_context.rb +15 -0
  109. metadata +168 -0
@@ -0,0 +1,352 @@
1
+ module FlipFab
2
+ describe ContextualFeature do
3
+ let(:override) { }
4
+ let(:default) { :disabled }
5
+ let(:persistence_adapters) { [TestPersistence] }
6
+ let(:feature) { Feature.new :example_feature, { default: default, persistence_adapters: persistence_adapters } }
7
+ let(:feature_states) {{ example_feature: :enabled }}
8
+ let(:context) { TestContext.new feature_states, { 'example_feature' => override } }
9
+ subject{ described_class.new feature, context }
10
+
11
+ describe '.new' do
12
+
13
+ it 'assigns the feature' do
14
+ expect(subject.feature).to eq(feature)
15
+ end
16
+
17
+ it 'assigns the context' do
18
+ expect(subject.context).to eq(context)
19
+ end
20
+
21
+ context 'when the feature has been overridden' do
22
+ let(:override) { 'disabled' }
23
+
24
+ it 'persists the override' do
25
+ expect{ subject }.to change{ feature_states }.from({ example_feature: :enabled }).to({ example_feature: :disabled })
26
+ end
27
+
28
+ context 'when the override provided is not one of enabled or disabled, it does not persist the override' do
29
+ let(:override) { '' }
30
+
31
+ it 'does not persist the override' do
32
+ expect{ subject }.not_to change{ feature_states }.from({ example_feature: :enabled })
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#enabled?' do
39
+
40
+ context 'when the feature is enabled in the adapter' do
41
+ let(:feature_states) {{ example_feature: :enabled }}
42
+
43
+ it 'returns true' do
44
+ expect(subject.enabled?).to be_truthy
45
+ end
46
+
47
+ context 'when the feature has been overridden' do
48
+ let(:override) { 'disabled' }
49
+
50
+ it 'returns false' do
51
+ expect(subject.enabled?).to be_falsey
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'when the feature is disabled in the adapter' do
57
+ let(:feature_states) {{ example_feature: :disabled }}
58
+
59
+ it 'returns false' do
60
+ expect(subject.enabled?).to be_falsey
61
+ end
62
+ end
63
+
64
+ context 'when the feature is not specified in the adapter' do
65
+ let(:feature_states) {{ }}
66
+
67
+ context 'when the default is :enabled' do
68
+ let(:default) { :enabled }
69
+
70
+ it 'returns true' do
71
+ expect(subject.enabled?).to be_truthy
72
+ end
73
+ end
74
+
75
+ context 'when the default is :disabled' do
76
+ let(:default) { :disabled }
77
+
78
+ it 'returns false' do
79
+ expect(subject.enabled?).to be_falsey
80
+ end
81
+ end
82
+ end
83
+
84
+ context 'when there are multiple adapters' do
85
+ let(:persistence_adapters) { [TestPersistence, TestMultiplePersistence] }
86
+
87
+ context 'when the first adapter has enabled and the second adapter has nil' do
88
+ let(:feature_states) {{ example_feature: :enabled, different_example_feature: nil }}
89
+
90
+ it 'returns true' do
91
+ expect(subject.enabled?).to be_truthy
92
+ end
93
+ end
94
+
95
+ context 'when the first adapter has nil and the second adapter has enabled' do
96
+ let(:feature_states) {{ example_feature: nil, different_example_feature: :enabled }}
97
+
98
+ it 'returns true' do
99
+ expect(subject.enabled?).to be_truthy
100
+ end
101
+ end
102
+
103
+ context 'when the first adapter has disabled and the second adapter has enabled' do
104
+ let(:feature_states) {{ example_feature: :disabled, different_example_feature: :enabled }}
105
+
106
+ it 'returns false' do
107
+ expect(subject.enabled?).to be_falsey
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#disabled?' do
114
+
115
+ context 'when #enabled? returns true' do
116
+ let(:feature_states) {{ example_feature: :enabled }}
117
+
118
+ it 'returns false' do
119
+ expect(subject.disabled?).to be_falsey
120
+ end
121
+ end
122
+
123
+ context 'when #enabled? returns false' do
124
+ let(:feature_states) {{ example_feature: :disabled }}
125
+
126
+ it 'returns true' do
127
+ expect(subject.disabled?).to be_truthy
128
+ end
129
+ end
130
+ end
131
+
132
+ describe '#state=' do
133
+
134
+ context 'when the provided value is not :enabled or :disabled' do
135
+
136
+ it 'raises' do
137
+ expect{ subject.state = '' }.to raise_error 'Invalid state provided: ``, possible states are :enabled, :disabled'
138
+ expect{ subject.state = 'enabled' }.to raise_error 'Invalid state provided: `enabled`, possible states are :enabled, :disabled'
139
+ end
140
+ end
141
+
142
+ context 'when the provided value is :enabled or :disabled' do
143
+
144
+ it 'changes the state of the feature' do
145
+ expect{ subject.state = :disabled }.to change{subject.enabled?}.from(true).to(false)
146
+ expect{ subject.state = :enabled }.to change{subject.enabled?}.from(false).to(true)
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '#enable' do
152
+
153
+ context 'when the state has been overridden' do
154
+ let(:override) { 'disabled' }
155
+
156
+ context 'and the persistence adapter has the opposite state' do
157
+ let(:feature_states) {{ example_feature: :disabled }}
158
+
159
+ it 'does not change the state of the feature' do
160
+ expect{subject.enable}.not_to change{subject.enabled?}.from(false)
161
+ end
162
+
163
+ it 'does not persist the state in the adapter' do
164
+ expect_any_instance_of(TestPersistence).not_to receive(:write).with(:enabled)
165
+ subject.enable
166
+ end
167
+ end
168
+ end
169
+
170
+ context 'when there are multiple persistence adapters' do
171
+ let(:persistence_adapters) { [TestPersistence, TestMultiplePersistence] }
172
+ let(:feature_states) {{ example_feature: :disabled, different_example_feature: :disabled }}
173
+
174
+ it 'changes the state of the feature' do
175
+ expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
176
+ end
177
+
178
+ it 'persists the state in the adapters' do
179
+ expect{ subject.enable }.to change{ feature_states }.from({ example_feature: :disabled, different_example_feature: :disabled }).to({ example_feature: :enabled, different_example_feature: :enabled })
180
+ end
181
+ end
182
+
183
+ context 'when there is a persistence adapter' do
184
+ let(:persistence_adapters) { [TestPersistence] }
185
+
186
+ context 'and the persistence adapter has the same state' do
187
+ let(:feature_states) {{ example_feature: :enabled }}
188
+
189
+ it 'does not change the state of the feature' do
190
+ expect{subject.enable}.not_to change{subject.enabled?}.from(true)
191
+ end
192
+ end
193
+
194
+ context 'and the persistence adapter has the opposite state' do
195
+ let(:feature_states) {{ example_feature: :disabled }}
196
+
197
+ it 'changes the state of the feature' do
198
+ expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
199
+ end
200
+
201
+ it 'persists the state in the adapter' do
202
+ expect{ subject.enable }.to change{ feature_states }.from({ example_feature: :disabled }).to({ example_feature: :enabled })
203
+ end
204
+ end
205
+
206
+ context 'and the persistence adapter has no state' do
207
+ let(:feature_states) {{ }}
208
+
209
+ context 'and the feature is disabled' do
210
+ let(:default) { :disabled }
211
+
212
+ it 'changes the state of the feature' do
213
+ expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
214
+ end
215
+
216
+ it 'persists the state in the adapter' do
217
+ expect{ subject.enable }.to change{ feature_states }.from({ }).to({ example_feature: :enabled })
218
+ end
219
+ end
220
+
221
+ context 'and the feature is enabled' do
222
+ let(:default) { :enabled }
223
+
224
+ it 'does not change the state of the feature' do
225
+ expect{subject.enable}.not_to change{subject.enabled?}.from(true)
226
+ end
227
+
228
+ it 'persists the state in the adapter' do
229
+ expect{ subject.enable }.to change{ feature_states }.from({ }).to({ example_feature: :enabled })
230
+ end
231
+ end
232
+ end
233
+
234
+ context 'when there is not a persistence adapter' do
235
+ let(:persistence_adapters) { [] }
236
+
237
+ context 'and the feature is enabled' do
238
+ let(:default) { :enabled }
239
+
240
+ it 'does not change the state of the feature' do
241
+ expect{subject.enable}.not_to change{subject.enabled?}.from(true)
242
+ end
243
+ end
244
+
245
+ context 'and the feature is disabled' do
246
+ let(:default) { :disabled }
247
+
248
+ it 'changes the state of the feature' do
249
+ expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+
257
+ describe '#disable' do
258
+
259
+ context 'when the state has been overridden' do
260
+ let(:override) { 'enabled' }
261
+
262
+ context 'and the persistence adapter has the opposite state' do
263
+ let(:feature_states) {{ example_feature: :enabled }}
264
+
265
+ it 'does not change the state of the feature' do
266
+ expect{subject.disable}.not_to change{subject.disabled?}.from(false)
267
+ end
268
+
269
+ it 'does not persist the state in the adapter' do
270
+ expect_any_instance_of(TestPersistence).not_to receive(:write).with(:disabled)
271
+ subject.disable
272
+ end
273
+ end
274
+ end
275
+
276
+ context 'when there is a persistence adapter' do
277
+ let(:persistence_adapters) { [TestPersistence] }
278
+
279
+ context 'and the persistence adapter has the same state' do
280
+ let(:feature_states) {{ example_feature: :disabled }}
281
+
282
+ it 'does not change the state of the feature' do
283
+ expect{subject.disable}.not_to change{subject.disabled?}.from(true)
284
+ end
285
+ end
286
+
287
+ context 'and the persistence adapter has the opposite state' do
288
+ let(:feature_states) {{ example_feature: :enabled }}
289
+
290
+ it 'changes the state of the feature' do
291
+ expect{subject.disable}.to change{subject.disabled?}.from(false).to(true)
292
+ end
293
+
294
+ it 'persists the state in the adapter' do
295
+ expect_any_instance_of(TestPersistence).to receive(:write).with(:disabled)
296
+ subject.disable
297
+ end
298
+ end
299
+
300
+ context 'and the persistence adapter has no state' do
301
+ let(:feature_states) {{ }}
302
+
303
+ context 'and the feature is enabled' do
304
+ let(:default) { :enabled }
305
+
306
+ it 'changes the state of the feature' do
307
+ expect{subject.disable}.to change{subject.disabled?}.from(false).to(true)
308
+ end
309
+
310
+ it 'persists the state in the adapter' do
311
+ expect_any_instance_of(TestPersistence).to receive(:write).with(:disabled)
312
+ subject.disable
313
+ end
314
+ end
315
+
316
+ context 'and the feature is disabled' do
317
+ let(:default) { :disabled }
318
+
319
+ it 'does not change the state of the feature' do
320
+ expect{subject.disable}.not_to change{subject.disabled?}.from(true)
321
+ end
322
+
323
+ it 'persists the state in the adapter' do
324
+ expect_any_instance_of(TestPersistence).to receive(:write).with(:disabled)
325
+ subject.disable
326
+ end
327
+ end
328
+ end
329
+
330
+ context 'when there is not a persistence adapter' do
331
+ let(:persistence_adapters) { [] }
332
+
333
+ context 'and the feature is disabled' do
334
+ let(:default) { :disabled }
335
+
336
+ it 'does not change the state of the feature' do
337
+ expect{subject.disable}.not_to change{subject.disabled?}.from(true)
338
+ end
339
+ end
340
+
341
+ context 'and the feature is enabled' do
342
+ let(:default) { :enabled }
343
+
344
+ it 'changes the state of the feature' do
345
+ expect{subject.disable}.to change{subject.disabled?}.from(false).to(true)
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
352
+ end
@@ -0,0 +1,50 @@
1
+ Feature: Persisting the feature state in a cookie
2
+
3
+ Background:
4
+ Given the host is 'www.simplybusiness.co.uk'
5
+ And the feature name is 'example_feature'
6
+ And the time is '2015-01-22 15:26:31 +0000'
7
+ And the state of the feature is 'enabled'
8
+
9
+ Scenario: The cookie should apply to the root path (/)
10
+ When I persist the feature state in a cookie
11
+ Then the cookie has the path '/'
12
+
13
+ Scenario Outline: The cookie should apply for all domains under the top-level domain (no domain for localhost or IP addresses, however)
14
+ Given the host is '<host>'
15
+ When I persist the feature state in a cookie
16
+ Then the cookie has the domain '<cookie domain>'
17
+
18
+ Examples:
19
+ | host | cookie domain |
20
+ | localhost | |
21
+ | 127.0.0.1 | |
22
+ | 192.168.2.40 | |
23
+ | www.simplybusiness.co.uk | .simplybusiness.co.uk |
24
+ | www.quote.simplybusiness.co.uk | .simplybusiness.co.uk |
25
+ | simplybusiness.co.uk | .simplybusiness.co.uk |
26
+ | www.simplybusiness.com | .simplybusiness.com |
27
+
28
+ Scenario Outline: The cookie should be named using the name of the gem and name of feature, concatenated with a dot
29
+ Given the feature name is '<feature name>'
30
+ When I persist the feature state in a cookie
31
+ Then the cookie has the name '<cookie name>'
32
+
33
+ Examples:
34
+ | feature name | cookie name |
35
+ | cool_new_feature | flip_fab.cool_new_feature |
36
+ | other_cool_new_feature | flip_fab.other_cool_new_feature |
37
+
38
+ Scenario: The cookie should expire after 1 year
39
+ When I persist the feature state in a cookie
40
+ Then the cookie expires at 'Fri, 22 Jan 2016 15:26:31 -0000'
41
+
42
+ Scenario Outline: The cookie's value should be the state of the feature
43
+ Given the state of the feature is '<feature state>'
44
+ When I persist the feature state in a cookie
45
+ Then the cookie value is '<feature state>'
46
+
47
+ Examples:
48
+ | feature state |
49
+ | enabled |
50
+ | disabled |
@@ -0,0 +1,90 @@
1
+ require 'rack'
2
+ require 'timecop'
3
+
4
+ module FlipFab
5
+ describe CookiePersistence do
6
+ let(:cookies) { }
7
+ let(:context) { TestRackContext.new cookies, 'simplybusiness.co.uk' }
8
+ before { FlipFab.define_feature :example_feature }
9
+ after { FlipFab.features.clear }
10
+ subject{ described_class.new :example_feature, context }
11
+
12
+ it 'runs the feature' do
13
+ feature
14
+ end
15
+
16
+ step 'the host is :host' do |host|
17
+ @host = host
18
+ end
19
+
20
+ step 'the feature name is :feature_name' do |feature_name|
21
+ @feature_name = feature_name
22
+ end
23
+
24
+ step 'the time is :current_time' do |current_time|
25
+ Timecop.freeze(Time.parse current_time)
26
+ end
27
+
28
+ step 'the state of the feature is :feature_state' do |feature_state|
29
+ @feature_state = feature_state
30
+ end
31
+
32
+ step 'I persist the feature state in a cookie' do
33
+ context = TestRackContext.new '', @host
34
+ (described_class.new @feature_name, context).write @feature_state
35
+ @cookie = context.response_cookies
36
+ end
37
+
38
+ step 'the cookie has the path :path' do |path|
39
+ expect(@cookie).to match(/path=#{path};/)
40
+ end
41
+
42
+ step 'the cookie has the domain :domain' do |domain|
43
+ if domain == ''
44
+ expect(@cookie).not_to match(/domain/)
45
+ else
46
+ expect(@cookie).to match(/domain=#{domain};/)
47
+ end
48
+ end
49
+
50
+ step 'the cookie has the name :name' do |name|
51
+ expect(@cookie).to match(/\A#{name}.*/)
52
+ end
53
+
54
+ step 'the cookie expires at :expiration' do |expiration|
55
+ expect(@cookie).to match(/expires=#{expiration}\Z/)
56
+ end
57
+
58
+ step 'the cookie value is :value' do |value|
59
+ expect(@cookie).to match(/\=#{value};/)
60
+ end
61
+
62
+ describe '#read' do
63
+
64
+ context 'when there is no existing cookie' do
65
+ let(:cookies) { }
66
+
67
+ it 'returns nil' do
68
+ expect(subject.read).to be_nil
69
+ end
70
+ end
71
+
72
+ context 'when the feature state is defined in the cookie' do
73
+ let(:cookies) { 'flip_fab.example_feature=enabled' }
74
+
75
+ it 'returns the feature state' do
76
+ expect(subject.read).to eq(:enabled)
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '#write' do
82
+ before { Timecop.freeze(Time.local(1990)) }
83
+ after { Timecop.return }
84
+
85
+ it 'saves the feature state' do
86
+ expect{ subject.write :enabled }.to change{ context.response_cookies }.from(nil).to('flip_fab.example_feature=enabled; domain=.simplybusiness.co.uk; path=/; expires=Tue, 01 Jan 1991 00:00:00 -0000')
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,86 @@
1
+ module FlipFab
2
+ describe Feature do
3
+ let(:name) { :example_test }
4
+ let(:options) {{ default: :enabled, persistence_adapters: [] }}
5
+ subject { described_class.new name, options }
6
+
7
+ describe '.new' do
8
+
9
+ it 'assigns the name' do
10
+ expect(subject.name).to eq(:example_test)
11
+ end
12
+
13
+ it 'assigns the default' do
14
+ expect(subject.default).to eq(:enabled)
15
+ end
16
+
17
+ it 'assigns the persistence adapters' do
18
+ expect(subject.persistence_adapters).to eq([])
19
+ end
20
+
21
+ context 'when the default is not provided' do
22
+ let(:options) { {} }
23
+
24
+ it 'assigns the default to :disabled' do
25
+ expect(subject.default).to eq(:disabled)
26
+ end
27
+ end
28
+
29
+ context 'when the persistence adapters are not provided' do
30
+ let(:options) { {} }
31
+
32
+ it 'uses a cookie adapter' do
33
+ expect(subject.persistence_adapters).to eq([CookiePersistence])
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#enabled?' do
39
+
40
+ context 'when the feature is enabled' do
41
+ let(:options) {{ default: :enabled, persistence_adapters: [] }}
42
+
43
+ it 'returns true' do
44
+ expect(subject.enabled?).to be_truthy
45
+ end
46
+ end
47
+
48
+ context 'when the feature is disabled' do
49
+ let(:options) {{ default: :disabled, persistence_adapters: [] }}
50
+
51
+ it 'returns false' do
52
+ expect(subject.enabled?).to be_falsey
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '#disabled?' do
58
+
59
+ context 'when the feature is disabled' do
60
+ let(:options) {{ default: :disabled, persistence_adapters: [] }}
61
+
62
+ it 'returns true' do
63
+ expect(subject.disabled?).to be_truthy
64
+ end
65
+ end
66
+
67
+ context 'when the feature is enabled' do
68
+ let(:options) {{ default: :enabled, persistence_adapters: [] }}
69
+
70
+ it 'returns false' do
71
+ expect(subject.disabled?).to be_falsey
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#with_context' do
77
+ let(:context) { double(:context) }
78
+
79
+ it 'returns a contextual feature' do
80
+ expect(subject.with_context context).to be_a ContextualFeature
81
+ expect((subject.with_context context).feature).to eq(subject)
82
+ expect((subject.with_context context).context).to eq(context)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,34 @@
1
+ module FlipFab
2
+ describe FeaturesByName do
3
+ let(:feature) { Feature.new :example_feature }
4
+ let(:features) { { example_feature: feature } }
5
+ subject{ described_class.new features }
6
+
7
+ describe '#[]' do
8
+
9
+ context 'when the feature exists' do
10
+
11
+ it 'returns the feature' do
12
+ expect(subject[:example_feature]).to eq(feature)
13
+ end
14
+ end
15
+
16
+ context 'when the feature does not exist' do
17
+
18
+ it 'raises' do
19
+ expect{ subject[:no_feature] }.to raise_error 'no feature has been defined with the name: no_feature'
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '#with_context' do
25
+ let(:context) { double(:context) }
26
+
27
+ it 'returns contextual features by name' do
28
+ expect(subject.with_context context).to be_a described_class
29
+ expect((subject.with_context context)[:example_feature]).to be_a ContextualFeature
30
+ expect((subject.with_context context)[:example_feature].feature).to eq(feature)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ Feature: Feature flipping in the context of a request, session, etc.
2
+
3
+ Scenario Outline: A feature can be flipped and remains flipped in the provided context
4
+ Given there is a feature with a default state of '<default_state>'
5
+ And there are two contexts
6
+ Then the feature is '<default_state>' in the first context, '<default_state>' in the second context
7
+ When I 'enable' the feature in the first context
8
+ Then the feature is 'enabled' in the first context, '<default_state>' in the second context
9
+ When I 'disable' the feature in the first context
10
+ Then the feature is 'disabled' in the first context, '<default_state>' in the second context
11
+
12
+ Examples:
13
+ | default_state |
14
+ | enabled |
15
+ | disabled |
16
+
17
+ Scenario Outline: A feature's state can be set in the URL parameters and remains in that state for that user
18
+ Given there is a feature with a default state of '<default_state>' with cookie persistence
19
+ When I override the state in the URL parameters with '<overridden_state>'
20
+ Then the feature is '<overridden_state>' for the user
21
+ When I 'enable' the feature for the user
22
+ Then the feature is '<overridden_state>' for the user
23
+ When I 'disable' the feature for the user
24
+ Then the feature is '<overridden_state>' for the user
25
+
26
+ Examples:
27
+ | default_state | overridden_state |
28
+ | enabled | enabled |
29
+ | disabled | disabled |
30
+ | enabled | disabled |
31
+ | disabled | enabled |