flipper 0.2.1 → 0.3.0

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.
@@ -0,0 +1,17 @@
1
+ require 'helper'
2
+ require 'flipper/key'
3
+
4
+ describe Flipper::Key do
5
+ subject { described_class.new(:foo, :bar) }
6
+
7
+ it "initializes with prefix and suffix" do
8
+ key = described_class.new(:foo, :bar)
9
+ key.should be_instance_of(described_class)
10
+ end
11
+
12
+ describe "#to_s" do
13
+ it "returns prefix and suffix joined by separator" do
14
+ subject.to_s.should eq("foo#{subject.separator}bar")
15
+ end
16
+ end
17
+ end
@@ -1,41 +1,22 @@
1
1
  require 'helper'
2
2
  require 'rack/test'
3
3
  require 'flipper/middleware/local_cache'
4
+ require 'flipper/adapters/operation_logger'
5
+ require 'flipper/adapters/memory'
4
6
 
5
7
  describe Flipper::Middleware::LocalCache do
6
8
  include Rack::Test::Methods
7
9
 
8
- class LoggedHash < Hash
9
- attr_reader :reads, :writes
10
-
11
- Read = Struct.new(:key)
12
- Write = Struct.new(:key, :value)
13
-
14
- def initialize(*args)
15
- @reads, @writes = [], []
16
- super
17
- end
18
-
19
- def [](key)
20
- @reads << Read.new(key)
21
- super
22
- end
23
-
24
- def []=(key, value)
25
- @writes << Write.new(key, value)
26
- super
27
- end
28
- end
29
-
30
10
  class Enum < Struct.new(:iter)
31
- def each(&b)
32
- iter.call(&b)
11
+ def each(&block)
12
+ iter.call(&block)
33
13
  end
34
14
  end
35
15
 
36
- let(:source) { LoggedHash.new }
37
- let(:adapter) { Flipper::Adapters::Memory.new(source) }
38
- let(:flipper) { Flipper.new(adapter) }
16
+ let(:source) { {} }
17
+ let(:memory_adapter) { Flipper::Adapters::Memory.new(source) }
18
+ let(:adapter) { Flipper::Adapters::OperationLogger.new(memory_adapter) }
19
+ let(:flipper) { Flipper.new(adapter) }
39
20
 
40
21
  let(:app) {
41
22
  # ensure scoped for builder block, annoying...
@@ -55,6 +36,10 @@ describe Flipper::Middleware::LocalCache do
55
36
  end.to_app
56
37
  }
57
38
 
39
+ before do
40
+ adapter.reset
41
+ end
42
+
58
43
  it "delegates" do
59
44
  called = false
60
45
  app = lambda { |env|
@@ -77,9 +62,9 @@ describe Flipper::Middleware::LocalCache do
77
62
 
78
63
  it "enables local cache for body each" do
79
64
  app = lambda { |env|
80
- [200, {}, Enum.new(lambda { |&b|
65
+ [200, {}, Enum.new(lambda { |&block|
81
66
  flipper.adapter.using_local_cache?.should be_true
82
- b.call "hello"
67
+ block.call "hello"
83
68
  })]
84
69
  }
85
70
  middleware = described_class.new app, flipper
@@ -110,6 +95,7 @@ describe Flipper::Middleware::LocalCache do
110
95
 
111
96
  it "really does cache" do
112
97
  flipper[:stats].enable
98
+ adapter.reset
113
99
 
114
100
  app = lambda { |env|
115
101
  flipper[:stats].enabled?
@@ -124,7 +110,9 @@ describe Flipper::Middleware::LocalCache do
124
110
  middleware = described_class.new app, flipper
125
111
  middleware.call({})
126
112
 
127
- source.reads.map(&:key).should eq(["stats/boolean"])
113
+ adapter.operations.should eq([
114
+ Flipper::Adapters::OperationLogger::Read.new("stats/boolean"),
115
+ ])
128
116
  end
129
117
 
130
118
  context "with a successful request" do
@@ -65,7 +65,7 @@ describe Flipper::Registry do
65
65
  end
66
66
 
67
67
  it "returns the keys" do
68
- subject.keys.should eq([:admins, :devs])
68
+ subject.keys.map(&:to_s).sort.should eq(['admins', 'devs'])
69
69
  end
70
70
  end
71
71
 
@@ -76,7 +76,7 @@ describe Flipper::Registry do
76
76
  end
77
77
 
78
78
  it "returns the values" do
79
- subject.values.should eq(['admins', 'devs'])
79
+ subject.values.map(&:to_s).sort.should eq(['admins', 'devs'])
80
80
  end
81
81
  end
82
82
 
@@ -95,8 +95,8 @@ describe Flipper::Registry do
95
95
  values << value
96
96
  end
97
97
 
98
- keys.should eq([:admins, :devs])
99
- values.should eq(['admins', 'devs'])
98
+ keys.map(&:to_s).sort.should eq(['admins', 'devs'])
99
+ values.sort.should eq(['admins', 'devs'])
100
100
  end
101
101
  end
102
102
 
@@ -31,17 +31,12 @@ describe Flipper::Types::Actor do
31
31
  described_class.wrappable?(thing).should be_true
32
32
  end
33
33
 
34
- it "returns true if responds to id" do
35
- thing = Struct.new(:id).new(10)
36
- described_class.wrappable?(thing).should be_true
37
- end
38
-
39
34
  it "returns true if responds to to_i" do
40
35
  described_class.wrappable?(1).should be_true
41
36
  end
42
37
 
43
- it "returns false if not actor and does not respond to identifier, id, nor to_i" do
44
- described_class.wrappable?(:nope).should be_false
38
+ it "returns false if not actor and does not respond to identifier or to_i" do
39
+ described_class.wrappable?(Object.new).should be_false
45
40
  end
46
41
  end
47
42
 
@@ -73,12 +68,6 @@ describe Flipper::Types::Actor do
73
68
  actor.identifier.should be(1)
74
69
  end
75
70
 
76
- it "initializes with object that responds to id" do
77
- thing = Struct.new(:id).new(13)
78
- actor = described_class.new(thing)
79
- actor.identifier.should be(13)
80
- end
81
-
82
71
  it "raises error when initialized with nil" do
83
72
  expect {
84
73
  described_class.new(nil)
@@ -104,7 +93,7 @@ describe Flipper::Types::Actor do
104
93
  describe "#respond_to?" do
105
94
  it "returns true if responds to method" do
106
95
  actor = described_class.new(10)
107
- actor.respond_to?(:enabled_value).should be_true
96
+ actor.respond_to?(:value).should be_true
108
97
  end
109
98
 
110
99
  it "returns true if thing responds to method" do
@@ -2,5 +2,26 @@ require 'helper'
2
2
  require 'flipper/types/percentage_of_actors'
3
3
 
4
4
  describe Flipper::Types::Percentage do
5
+ subject {
6
+ described_class.new(5)
7
+ }
5
8
  it_should_behave_like 'a percentage'
9
+
10
+ describe "#eql?" do
11
+ it "returns true for same class and value" do
12
+ subject.eql?(described_class.new(subject.value)).should be_true
13
+ end
14
+
15
+ it "returns false for different value" do
16
+ subject.eql?(described_class.new(subject.value + 1)).should be_false
17
+ end
18
+
19
+ it "returns false for different class" do
20
+ subject.eql?(Object.new).should be_false
21
+ end
22
+
23
+ it "is aliased to ==" do
24
+ (subject == described_class.new(subject.value)).should be_true
25
+ end
26
+ end
6
27
  end
@@ -0,0 +1,455 @@
1
+ require 'helper'
2
+ require 'flipper/feature'
3
+ require 'flipper/adapters/memory'
4
+
5
+ describe Flipper::Feature do
6
+ subject { described_class.new(:search, adapter) }
7
+
8
+ let(:source) { {} }
9
+ let(:adapter) { Flipper::Adapters::Memory.new(source) }
10
+
11
+ let(:admin_group) { Flipper.group(:admins) }
12
+ let(:dev_group) { Flipper.group(:devs) }
13
+
14
+ let(:admin_thing) { double 'Non Flipper Thing', :identifier => 1, :admin? => true, :dev? => false }
15
+ let(:dev_thing) { double 'Non Flipper Thing', :identifier => 10, :admin? => false, :dev? => true }
16
+
17
+ let(:pitt) { Flipper::Types::Actor.new(1) }
18
+ let(:clooney) { Flipper::Types::Actor.new(10) }
19
+
20
+ let(:five_percent_of_actors) { Flipper::Types::PercentageOfActors.new(5) }
21
+ let(:five_percent_of_random) { Flipper::Types::PercentageOfRandom.new(5) }
22
+
23
+ before do
24
+ Flipper.register(:admins) { |thing| thing.admin? }
25
+ Flipper.register(:devs) { |thing| thing.dev? }
26
+ end
27
+
28
+ def enable_feature(feature)
29
+ key = Flipper::Key.new(subject.name, Flipper::Gates::Boolean::Key)
30
+ adapter.write key, true
31
+ end
32
+
33
+ def enable_group(group)
34
+ key = Flipper::Key.new(subject.name, Flipper::Gates::Group::Key)
35
+ name = group.respond_to?(:name) ? group.name : group
36
+ adapter.set_add key, name
37
+ end
38
+
39
+ def enable_actor(actor)
40
+ key = Flipper::Key.new(subject.name, Flipper::Gates::Actor::Key)
41
+ adapter.set_add key, actor.identifier
42
+ end
43
+
44
+ def enable_percentage_of_actors(percentage)
45
+ key = Flipper::Key.new(subject.name, Flipper::Gates::PercentageOfActors::Key)
46
+ adapter.write key, percentage.value
47
+ end
48
+
49
+ def enable_percentage_of_random(percentage)
50
+ key = Flipper::Key.new(subject.name, Flipper::Gates::PercentageOfRandom::Key)
51
+ adapter.write key, percentage.value
52
+ end
53
+
54
+ describe "#enable" do
55
+ context "with no arguments" do
56
+ before do
57
+ subject.enable
58
+ end
59
+
60
+ it "enables feature for all" do
61
+ subject.enabled?.should be_true
62
+ end
63
+
64
+ it "adds feature to set of features" do
65
+ adapter.set_members('features').should include('search')
66
+ end
67
+ end
68
+
69
+ context "with a group" do
70
+ before do
71
+ subject.enable(admin_group)
72
+ end
73
+
74
+ it "enables feature for non flipper thing in group" do
75
+ subject.enabled?(admin_thing).should be_true
76
+ end
77
+
78
+ it "does not enable feature for non flipper thing in other group" do
79
+ subject.enabled?(dev_thing).should be_false
80
+ end
81
+
82
+ it "enables feature for flipper actor in group" do
83
+ subject.enabled?(Flipper::Types::Actor.new(admin_thing)).should be_true
84
+ end
85
+
86
+ it "does not enable for flipper actor not in group" do
87
+ subject.enabled?(Flipper::Types::Actor.new(dev_thing)).should be_false
88
+ end
89
+
90
+ it "does not enable feature for all" do
91
+ subject.enabled?.should be_false
92
+ end
93
+
94
+ it "adds feature to set of features" do
95
+ adapter.set_members('features').should include('search')
96
+ end
97
+ end
98
+
99
+ context "with an actor" do
100
+ before do
101
+ subject.enable(pitt)
102
+ end
103
+
104
+ it "enables feature for actor" do
105
+ subject.enabled?(pitt).should be_true
106
+ end
107
+
108
+ it "does not enable feature for other actors" do
109
+ subject.enabled?(clooney).should be_false
110
+ end
111
+
112
+ it "adds feature to set of features" do
113
+ adapter.set_members('features').should include('search')
114
+ end
115
+ end
116
+
117
+ context "with a percentage of actors" do
118
+ before do
119
+ subject.enable(five_percent_of_actors)
120
+ end
121
+
122
+ it "enables feature for actor within percentage" do
123
+ enabled = (1..100).select { |i| subject.enabled?(Flipper::Types::Actor.new(i)) }.length
124
+ enabled.should be_within(2).of(5)
125
+ end
126
+
127
+ it "adds feature to set of features" do
128
+ adapter.set_members('features').should include('search')
129
+ end
130
+ end
131
+
132
+ context "with a percentage of random" do
133
+ before do
134
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
135
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
136
+ subject.enable(five_percent_of_random)
137
+ end
138
+
139
+ it "enables feature for time within percentage" do
140
+ @gate.stub(:rand => 0.04)
141
+ subject.enabled?.should be_true
142
+ end
143
+
144
+ it "does not enable feature for time not within percentage" do
145
+ @gate.stub(:rand => 0.10)
146
+ subject.enabled?.should be_false
147
+ end
148
+
149
+ it "adds feature to set of features" do
150
+ adapter.set_members('features').should include('search')
151
+ end
152
+ end
153
+
154
+ context "with argument that has no gate" do
155
+ it "raises error" do
156
+ thing = Object.new
157
+ expect {
158
+ subject.enable(thing)
159
+ }.to raise_error(Flipper::GateNotFound, "Could not find gate for #{thing.inspect}")
160
+ end
161
+ end
162
+ end
163
+
164
+ describe "#disable" do
165
+ context "with no arguments" do
166
+ before do
167
+ # ensures that random gate is stubbed with result that would be true for pitt
168
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
169
+ @gate.stub(:rand => 0.04)
170
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
171
+ enable_group admin_group
172
+ enable_actor pitt
173
+ enable_percentage_of_actors five_percent_of_actors
174
+ enable_percentage_of_random five_percent_of_random
175
+ subject.disable
176
+ end
177
+
178
+ it "disables feature" do
179
+ subject.enabled?.should be_false
180
+ end
181
+
182
+ it "disables for individual actor" do
183
+ subject.enabled?(pitt).should be_false
184
+ end
185
+
186
+ it "disables actor in group" do
187
+ subject.enabled?(admin_thing).should be_false
188
+ end
189
+
190
+ it "disables actor in percentage of actors" do
191
+ enabled = (1..100).select { |i| subject.enabled?(Flipper::Types::Actor.new(i)) }.length
192
+ enabled.should be(0)
193
+ end
194
+
195
+ it "disables percentage of random" do
196
+ subject.enabled?(pitt).should be_false
197
+ end
198
+
199
+ it "adds feature to set of features" do
200
+ adapter.set_members('features').should include('search')
201
+ end
202
+ end
203
+
204
+ context "with a group" do
205
+ before do
206
+ enable_group dev_group
207
+ enable_group admin_group
208
+ subject.disable(admin_group)
209
+ end
210
+
211
+ it "disables the feature for non flipper thing in the group" do
212
+ subject.enabled?(admin_thing).should be_false
213
+ end
214
+
215
+ it "does not disable feature for non flipper thing in other groups" do
216
+ subject.enabled?(dev_thing).should be_true
217
+ end
218
+
219
+ it "disables feature for flipper actor in group" do
220
+ subject.enabled?(Flipper::Types::Actor.new(admin_thing)).should be_false
221
+ end
222
+
223
+ it "does not disable feature for flipper actor in other groups" do
224
+ subject.enabled?(Flipper::Types::Actor.new(dev_thing)).should be_true
225
+ end
226
+
227
+ it "adds feature to set of features" do
228
+ adapter.set_members('features').should include('search')
229
+ end
230
+ end
231
+
232
+ context "with an actor" do
233
+ before do
234
+ enable_actor pitt
235
+ enable_actor clooney
236
+ subject.disable(pitt)
237
+ end
238
+
239
+ it "disables feature for actor" do
240
+ subject.enabled?(pitt).should be_false
241
+ end
242
+
243
+ it "does not disable feature for other actors" do
244
+ subject.enabled?(clooney).should be_true
245
+ end
246
+
247
+ it "adds feature to set of features" do
248
+ adapter.set_members('features').should include('search')
249
+ end
250
+ end
251
+
252
+ context "with a percentage of actors" do
253
+ before do
254
+ subject.disable(five_percent_of_actors)
255
+ end
256
+
257
+ it "disables feature" do
258
+ enabled = (1..100).select { |i| subject.enabled?(Flipper::Types::Actor.new(i)) }.length
259
+ enabled.should be(0)
260
+ end
261
+
262
+ it "adds feature to set of features" do
263
+ adapter.set_members('features').should include('search')
264
+ end
265
+ end
266
+
267
+ context "with a percentage of time" do
268
+ before do
269
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
270
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
271
+ subject.disable(five_percent_of_random)
272
+ end
273
+
274
+ it "disables feature for time within percentage" do
275
+ @gate.stub(:rand => 0.04)
276
+ subject.enabled?.should be_false
277
+ end
278
+
279
+ it "disables feature for time not within percentage" do
280
+ @gate.stub(:rand => 0.10)
281
+ subject.enabled?.should be_false
282
+ end
283
+
284
+ it "adds feature to set of features" do
285
+ adapter.set_members('features').should include('search')
286
+ end
287
+ end
288
+
289
+ context "with argument that has no gate" do
290
+ it "raises error" do
291
+ thing = Object.new
292
+ expect {
293
+ subject.disable(thing)
294
+ }.to raise_error(Flipper::GateNotFound, "Could not find gate for #{thing.inspect}")
295
+ end
296
+ end
297
+ end
298
+
299
+ describe "#enabled?" do
300
+ context "with no arguments" do
301
+ it "defaults to false" do
302
+ subject.enabled?.should be_false
303
+ end
304
+ end
305
+
306
+ context "with no arguments, but boolean enabled" do
307
+ before do
308
+ enable_feature subject
309
+ end
310
+
311
+ it "returns true" do
312
+ subject.enabled?.should be_true
313
+ end
314
+ end
315
+
316
+ context "for actor in enabled group" do
317
+ before do
318
+ enable_group admin_group
319
+ end
320
+
321
+ it "returns true" do
322
+ subject.enabled?(Flipper::Types::Actor.new(admin_thing)).should be_true
323
+ end
324
+ end
325
+
326
+ context "for actor in disabled group" do
327
+ it "returns false" do
328
+ subject.enabled?(Flipper::Types::Actor.new(dev_thing)).should be_false
329
+ end
330
+ end
331
+
332
+ context "for enabled actor" do
333
+ before do
334
+ enable_actor pitt
335
+ end
336
+
337
+ it "returns true" do
338
+ subject.enabled?(pitt).should be_true
339
+ end
340
+ end
341
+
342
+ context "for not enabled actor" do
343
+ it "returns false" do
344
+ subject.enabled?(clooney).should be_false
345
+ end
346
+
347
+ it "returns true if boolean enabled" do
348
+ enable_feature subject
349
+ subject.enabled?(clooney).should be_true
350
+ end
351
+ end
352
+
353
+ context "during enabled percentage of time" do
354
+ before do
355
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
356
+ @gate.stub(:rand => 0.04)
357
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
358
+ enable_percentage_of_random five_percent_of_random
359
+ end
360
+
361
+ it "returns true" do
362
+ subject.enabled?.should be_true
363
+ subject.enabled?(nil).should be_true
364
+ subject.enabled?(pitt).should be_true
365
+ subject.enabled?(admin_thing).should be_true
366
+ end
367
+ end
368
+
369
+ context "during not enabled percentage of time" do
370
+ before do
371
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
372
+ @gate.stub(:rand => 0.10)
373
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
374
+ enable_percentage_of_random five_percent_of_random
375
+ end
376
+
377
+ it "returns false" do
378
+ subject.enabled?.should be_false
379
+ subject.enabled?(nil).should be_false
380
+ subject.enabled?(pitt).should be_false
381
+ subject.enabled?(admin_thing).should be_false
382
+ end
383
+
384
+ it "returns true if boolean enabled" do
385
+ enable_feature subject
386
+ subject.enabled?.should be_true
387
+ subject.enabled?(nil).should be_true
388
+ subject.enabled?(pitt).should be_true
389
+ subject.enabled?(admin_thing).should be_true
390
+ end
391
+ end
392
+
393
+ context "for a non flipper thing" do
394
+ before do
395
+ enable_group admin_group
396
+ end
397
+
398
+ it "returns true if in enabled group" do
399
+ subject.enabled?(admin_thing).should be_true
400
+ end
401
+
402
+ it "returns false if not in enabled group" do
403
+ subject.enabled?(dev_thing).should be_false
404
+ end
405
+
406
+ it "returns true if boolean enabled" do
407
+ enable_feature subject
408
+ subject.enabled?(admin_thing).should be_true
409
+ subject.enabled?(dev_thing).should be_true
410
+ end
411
+ end
412
+
413
+ context "for a non flipper thing that does not respond to something in group block" do
414
+ let(:actor) { double('Actor') }
415
+
416
+ before do
417
+ boomboom = Flipper.register(:boomboom) { |actor| actor.boomboom? }
418
+ enable_group boomboom
419
+ end
420
+
421
+ it "returns false" do
422
+ expect { subject.enabled?(actor) }.to raise_error
423
+ end
424
+ end
425
+
426
+ context "for a non flipper thing when group in adapter, but not defined in code" do
427
+ let(:actor) { double('Actor') }
428
+
429
+ before do
430
+ enable_group :support
431
+ end
432
+
433
+ it "returns false" do
434
+ subject.enabled?(actor).should be_false
435
+ end
436
+ end
437
+ end
438
+
439
+ context "enabling multiple groups, disabling everything, then enabling one group" do
440
+ before do
441
+ subject.enable(admin_group)
442
+ subject.enable(dev_group)
443
+ subject.disable
444
+ subject.enable(admin_group)
445
+ end
446
+
447
+ it "enables feature for object in enabled group" do
448
+ subject.enabled?(admin_thing).should be_true
449
+ end
450
+
451
+ it "does not enable feature for object in not enabled group" do
452
+ subject.enabled?(dev_thing).should be_false
453
+ end
454
+ end
455
+ end