flipper 0.1.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.
Files changed (52) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +20 -0
  3. data/Guardfile +18 -0
  4. data/LICENSE +22 -0
  5. data/README.md +182 -0
  6. data/Rakefile +7 -0
  7. data/examples/basic.rb +27 -0
  8. data/examples/dsl.rb +85 -0
  9. data/examples/example_setup.rb +8 -0
  10. data/examples/group.rb +36 -0
  11. data/examples/individual_actor.rb +29 -0
  12. data/examples/percentage_of_actors.rb +35 -0
  13. data/examples/percentage_of_random.rb +33 -0
  14. data/flipper.gemspec +17 -0
  15. data/lib/flipper.rb +33 -0
  16. data/lib/flipper/adapters/memory.rb +35 -0
  17. data/lib/flipper/dsl.rb +61 -0
  18. data/lib/flipper/errors.rb +11 -0
  19. data/lib/flipper/feature.rb +49 -0
  20. data/lib/flipper/gate.rb +55 -0
  21. data/lib/flipper/gates/actor.rb +29 -0
  22. data/lib/flipper/gates/boolean.rb +29 -0
  23. data/lib/flipper/gates/group.rb +32 -0
  24. data/lib/flipper/gates/percentage_of_actors.rb +25 -0
  25. data/lib/flipper/gates/percentage_of_random.rb +25 -0
  26. data/lib/flipper/registry.rb +36 -0
  27. data/lib/flipper/spec/shared_adapter_specs.rb +78 -0
  28. data/lib/flipper/toggle.rb +31 -0
  29. data/lib/flipper/toggles/boolean.rb +22 -0
  30. data/lib/flipper/toggles/set.rb +17 -0
  31. data/lib/flipper/toggles/value.rb +17 -0
  32. data/lib/flipper/type.rb +18 -0
  33. data/lib/flipper/types/actor.rb +17 -0
  34. data/lib/flipper/types/boolean.rb +13 -0
  35. data/lib/flipper/types/group.rb +22 -0
  36. data/lib/flipper/types/percentage.rb +19 -0
  37. data/lib/flipper/types/percentage_of_actors.rb +6 -0
  38. data/lib/flipper/types/percentage_of_random.rb +6 -0
  39. data/lib/flipper/version.rb +3 -0
  40. data/spec/flipper/adapters/memory_spec.rb +19 -0
  41. data/spec/flipper/dsl_spec.rb +185 -0
  42. data/spec/flipper/feature_spec.rb +401 -0
  43. data/spec/flipper/registry_spec.rb +71 -0
  44. data/spec/flipper/types/actor_spec.rb +23 -0
  45. data/spec/flipper/types/boolean_spec.rb +9 -0
  46. data/spec/flipper/types/group_spec.rb +32 -0
  47. data/spec/flipper/types/percentage_of_actors_spec.rb +6 -0
  48. data/spec/flipper/types/percentage_of_random_spec.rb +6 -0
  49. data/spec/flipper/types/percentage_spec.rb +6 -0
  50. data/spec/flipper_spec.rb +59 -0
  51. data/spec/helper.rb +47 -0
  52. metadata +114 -0
@@ -0,0 +1,401 @@
1
+ require 'helper'
2
+ require 'flipper/feature'
3
+ require 'flipper/adapters/memory'
4
+
5
+ describe Flipper::Feature do
6
+ subject { Flipper::Feature.new(:search, adapter) }
7
+
8
+ let(:source) { {} }
9
+ let(:adapter) { Flipper::Adapters::Memory.new(source) }
10
+
11
+ let(:actor_key) { Flipper::Gates::Actor::Key }
12
+ let(:boolean_key) { Flipper::Gates::Boolean::Key }
13
+ let(:group_key) { Flipper::Gates::Group::Key }
14
+
15
+ let(:admin_group) { Flipper.group(:admins) }
16
+ let(:dev_group) { Flipper.group(:devs) }
17
+
18
+ let(:admin_thing) { double 'Non Flipper Thing', :admin? => true, :dev? => false }
19
+ let(:dev_thing) { double 'Non Flipper Thing', :admin? => false, :dev? => true }
20
+
21
+ let(:pitt) { Flipper::Types::Actor.new(1) }
22
+ let(:clooney) { Flipper::Types::Actor.new(10) }
23
+
24
+ let(:five_percent_of_actors) { Flipper::Types::PercentageOfActors.new(5) }
25
+ let(:percentage_of_actors_key) { Flipper::Gates::PercentageOfActors::Key }
26
+
27
+ let(:five_percent_of_random) { Flipper::Types::PercentageOfRandom.new(5) }
28
+ let(:percentage_of_random_key) { Flipper::Gates::PercentageOfRandom::Key }
29
+
30
+ before do
31
+ Flipper.register(:admins) { |thing| thing.admin? }
32
+ Flipper.register(:devs) { |thing| thing.dev? }
33
+ end
34
+
35
+ it "initializes with name and adapter" do
36
+ feature = Flipper::Feature.new(:search, adapter)
37
+ feature.should be_instance_of(Flipper::Feature)
38
+ end
39
+
40
+ describe "#name" do
41
+ it "returns name" do
42
+ subject.name.should eq(:search)
43
+ end
44
+ end
45
+
46
+ describe "#adapter" do
47
+ it "returns adapter" do
48
+ subject.adapter.should eq(adapter)
49
+ end
50
+ end
51
+
52
+ describe "#enable" do
53
+ context "with no arguments" do
54
+ before do
55
+ subject.enable
56
+ end
57
+
58
+ it "enables feature for all" do
59
+ subject.enabled?.should be_true
60
+ end
61
+ end
62
+
63
+ context "with a group" do
64
+ before do
65
+ subject.enable(admin_group)
66
+ end
67
+
68
+ it "enables feature for non flipper thing in group" do
69
+ subject.enabled?(admin_thing).should be_true
70
+ end
71
+
72
+ it "does not enable feature for non flipper thing in other group" do
73
+ subject.enabled?(dev_thing).should be_false
74
+ end
75
+
76
+ it "does not enable feature for all" do
77
+ subject.enabled?.should be_false
78
+ end
79
+ end
80
+
81
+ context "with an actor" do
82
+ before do
83
+ subject.enable(pitt)
84
+ end
85
+
86
+ it "enables feature for actor" do
87
+ subject.enabled?(pitt).should be_true
88
+ end
89
+
90
+ it "does not enable feature for other actors" do
91
+ subject.enabled?(clooney).should be_false
92
+ end
93
+ end
94
+
95
+ context "with a percentage of actors" do
96
+ before do
97
+ subject.enable(five_percent_of_actors)
98
+ end
99
+
100
+ it "enables feature for actor within percentage" do
101
+ subject.enabled?(pitt).should be_true
102
+ end
103
+
104
+ it "does not enable feature for actors not within percentage" do
105
+ subject.enabled?(clooney).should be_false
106
+ end
107
+ end
108
+
109
+ context "with a percentage of time" do
110
+ before do
111
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
112
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
113
+ subject.enable(five_percent_of_random)
114
+ end
115
+
116
+ it "enables feature for time within percentage" do
117
+ @gate.stub(:rand => 0.04)
118
+ subject.enabled?.should be_true
119
+ end
120
+
121
+ it "does not enable feature for time not within percentage" do
122
+ @gate.stub(:rand => 0.10)
123
+ subject.enabled?.should be_false
124
+ end
125
+ end
126
+
127
+ context "with argument that has no gate" do
128
+ it "raises error" do
129
+ thing = Object.new
130
+ expect {
131
+ subject.enable(thing)
132
+ }.to raise_error(Flipper::GateNotFound, "Could not find gate for #{thing.inspect}")
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "#disable" do
138
+ context "with no arguments" do
139
+ before do
140
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{group_key}", admin_group.name)
141
+ subject.disable
142
+ end
143
+
144
+ it "disables feature" do
145
+ subject.enabled?.should be_false
146
+ end
147
+
148
+ it "disables feature for non flipper thing in previously enabled groups" do
149
+ subject.enabled?(admin_thing).should be_false
150
+ end
151
+ end
152
+
153
+ context "with a group" do
154
+ before do
155
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{group_key}", dev_group.name)
156
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{group_key}", admin_group.name)
157
+ subject.disable(admin_group)
158
+ end
159
+
160
+ it "disables the feature for non flipper thing in the group" do
161
+ subject.enabled?(admin_thing).should be_false
162
+ end
163
+
164
+ it "does not disable feature for non flipper thing in other groups" do
165
+ subject.enabled?(dev_thing).should be_true
166
+ end
167
+ end
168
+
169
+ context "with an actor" do
170
+ before do
171
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{actor_key}", pitt.identifier)
172
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{actor_key}", clooney.identifier)
173
+ subject.disable(pitt)
174
+ end
175
+
176
+ it "disables feature for actor" do
177
+ subject.enabled?(pitt).should be_false
178
+ end
179
+
180
+ it "does not disable feature for other actors" do
181
+ subject.enabled?(clooney).should be_true
182
+ end
183
+ end
184
+
185
+ context "with a percentage of actors" do
186
+ before do
187
+ subject.disable(five_percent_of_actors)
188
+ end
189
+
190
+ it "disables feature for actor within percentage" do
191
+ subject.enabled?(pitt).should be_false
192
+ end
193
+
194
+ it "disables feature for actors not within percentage" do
195
+ subject.enabled?(clooney).should be_false
196
+ end
197
+ end
198
+
199
+ context "with a percentage of time" do
200
+ before do
201
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
202
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
203
+ subject.disable(five_percent_of_random)
204
+ end
205
+
206
+ it "disables feature for time within percentage" do
207
+ @gate.stub(:rand => 0.04)
208
+ subject.enabled?.should be_false
209
+ end
210
+
211
+ it "disables feature for time not within percentage" do
212
+ @gate.stub(:rand => 0.10)
213
+ subject.enabled?.should be_false
214
+ end
215
+ end
216
+
217
+ context "with argument that has no gate" do
218
+ it "raises error" do
219
+ thing = Object.new
220
+ expect {
221
+ subject.disable(thing)
222
+ }.to raise_error(Flipper::GateNotFound, "Could not find gate for #{thing.inspect}")
223
+ end
224
+ end
225
+ end
226
+
227
+ describe "#enabled?" do
228
+ context "with no arguments" do
229
+ it "defaults to false" do
230
+ subject.enabled?.should be_false
231
+ end
232
+ end
233
+
234
+ context "with no arguments, but boolean enabled" do
235
+ before do
236
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{boolean_key}", true)
237
+ end
238
+
239
+ it "returns true" do
240
+ subject.enabled?.should be_true
241
+ end
242
+ end
243
+
244
+ context "for enabled actor" do
245
+ before do
246
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{actor_key}", pitt.identifier)
247
+ end
248
+
249
+ it "returns true" do
250
+ subject.enabled?(pitt).should be_true
251
+ end
252
+ end
253
+
254
+ context "for not enabled actor" do
255
+ it "returns false" do
256
+ subject.enabled?(clooney).should be_false
257
+ end
258
+
259
+ it "returns true if boolean enabled" do
260
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{boolean_key}", true)
261
+ subject.enabled?(clooney).should be_true
262
+ end
263
+ end
264
+
265
+ context "for actor in percentage of actors enabled" do
266
+ before do
267
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{percentage_of_actors_key}", five_percent_of_actors.value)
268
+ end
269
+
270
+ it "returns true" do
271
+ subject.enabled?(pitt).should be_true
272
+ end
273
+ end
274
+
275
+ context "for actor not in percentage of actors enabled" do
276
+ before do
277
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{percentage_of_actors_key}", five_percent_of_actors.value)
278
+ end
279
+
280
+ it "returns false" do
281
+ subject.enabled?(clooney).should be_false
282
+ end
283
+
284
+ it "returns true if boolean enabled" do
285
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{boolean_key}", true)
286
+ subject.enabled?(clooney).should be_true
287
+ end
288
+ end
289
+
290
+ context "during enabled percentage of time" do
291
+ before do
292
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
293
+ @gate.stub(:rand => 0.04)
294
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
295
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{percentage_of_random_key}", five_percent_of_random.value)
296
+ end
297
+
298
+ it "returns true" do
299
+ subject.enabled?.should be_true
300
+ subject.enabled?(nil).should be_true
301
+ subject.enabled?(pitt).should be_true
302
+ subject.enabled?(admin_thing).should be_true
303
+ end
304
+ end
305
+
306
+ context "during not enabled percentage of time" do
307
+ before do
308
+ @gate = Flipper::Gates::PercentageOfRandom.new(subject)
309
+ @gate.stub(:rand => 0.10)
310
+ Flipper::Gates::PercentageOfRandom.should_receive(:new).and_return(@gate)
311
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{percentage_of_random_key}", five_percent_of_random.value)
312
+ end
313
+
314
+ it "returns false" do
315
+ subject.enabled?.should be_false
316
+ subject.enabled?(nil).should be_false
317
+ subject.enabled?(pitt).should be_false
318
+ subject.enabled?(admin_thing).should be_false
319
+ end
320
+
321
+ it "returns true if boolean enabled" do
322
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{boolean_key}", true)
323
+ subject.enabled?.should be_true
324
+ subject.enabled?(nil).should be_true
325
+ subject.enabled?(pitt).should be_true
326
+ subject.enabled?(admin_thing).should be_true
327
+ end
328
+ end
329
+
330
+ context "for a non flipper thing" do
331
+ before do
332
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{group_key}", admin_group.name)
333
+ end
334
+
335
+ it "returns true if in enabled group" do
336
+ subject.enabled?(admin_thing).should be_true
337
+ end
338
+
339
+ it "returns false if not in enabled group" do
340
+ subject.enabled?(dev_thing).should be_false
341
+ end
342
+
343
+ it "returns true if boolean enabled" do
344
+ adapter.write("#{subject.name}#{Flipper::Gate::Separator}#{boolean_key}", true)
345
+ subject.enabled?(admin_thing).should be_true
346
+ subject.enabled?(dev_thing).should be_true
347
+ end
348
+ end
349
+
350
+ context "for a non flipper thing that does not respond to something in group block" do
351
+ let(:actor) { double('Actor') }
352
+
353
+ before do
354
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{group_key}", admin_group.name)
355
+ end
356
+
357
+ it "returns false" do
358
+ expect { subject.enabled?(actor) }.to raise_error
359
+ end
360
+ end
361
+
362
+ context "for a non flipper thing when group in adapter, but not defined in code" do
363
+ let(:actor) { double('Actor') }
364
+
365
+ before do
366
+ adapter.set_add("#{subject.name}#{Flipper::Gate::Separator}#{group_key}", :support)
367
+ end
368
+
369
+ it "returns false" do
370
+ subject.enabled?(actor).should be_false
371
+ end
372
+ end
373
+ end
374
+
375
+ context "#disabled?" do
376
+ it "returns the opposite of enabled" do
377
+ subject.stub(:enabled? => true)
378
+ subject.disabled?.should be_false
379
+
380
+ subject.stub(:enabled? => false)
381
+ subject.disabled?.should be_true
382
+ end
383
+ end
384
+
385
+ context "enabling multiple groups, disabling everything, then enabling one group" do
386
+ before do
387
+ subject.enable(admin_group)
388
+ subject.enable(dev_group)
389
+ subject.disable
390
+ subject.enable(admin_group)
391
+ end
392
+
393
+ it "enables feature for object in enabled group" do
394
+ subject.enabled?(admin_thing).should be_true
395
+ end
396
+
397
+ it "does not enable feature for object in not enabled group" do
398
+ subject.enabled?(dev_thing).should be_false
399
+ end
400
+ end
401
+ end
@@ -0,0 +1,71 @@
1
+ require 'helper'
2
+ require 'flipper/registry'
3
+
4
+ describe Flipper::Registry do
5
+ subject { Flipper::Registry.new(source) }
6
+
7
+ let(:source) { {} }
8
+
9
+ describe "#add" do
10
+ it "adds to source" do
11
+ value = 'thing'
12
+ subject.add(:admins, value)
13
+ source[:admins].should eq(value)
14
+ end
15
+
16
+ it "raises exception if key already registered" do
17
+ subject.add(:admins, 'thing')
18
+
19
+ expect {
20
+ subject.add(:admins, 'again')
21
+ }.to raise_error(Flipper::Registry::DuplicateKey)
22
+ end
23
+ end
24
+
25
+ describe "#get" do
26
+ context "key registered" do
27
+ before do
28
+ source[:admins] = 'thing'
29
+ end
30
+
31
+ it "returns value" do
32
+ subject.get(:admins).should eq('thing')
33
+ end
34
+ end
35
+
36
+ context "key not registered" do
37
+ it "raises key not found" do
38
+ subject.get(:admins).should be_nil
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "#each" do
44
+ before do
45
+ source[:admins] = 'admins'
46
+ source[:devs] = 'devs'
47
+ end
48
+
49
+ it "iterates source keys and values" do
50
+ results = {}
51
+ subject.each do |key, value|
52
+ results[key] = value
53
+ end
54
+ results.should eq({
55
+ :admins => 'admins',
56
+ :devs => 'devs',
57
+ })
58
+ end
59
+ end
60
+
61
+ describe "#clear" do
62
+ before do
63
+ source[:admins] = 'admins'
64
+ end
65
+
66
+ it "clears the source" do
67
+ subject.clear
68
+ source.should be_empty
69
+ end
70
+ end
71
+ end