flipper 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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