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.
- data/.gitignore +18 -0
- data/Gemfile +20 -0
- data/Guardfile +18 -0
- data/LICENSE +22 -0
- data/README.md +182 -0
- data/Rakefile +7 -0
- data/examples/basic.rb +27 -0
- data/examples/dsl.rb +85 -0
- data/examples/example_setup.rb +8 -0
- data/examples/group.rb +36 -0
- data/examples/individual_actor.rb +29 -0
- data/examples/percentage_of_actors.rb +35 -0
- data/examples/percentage_of_random.rb +33 -0
- data/flipper.gemspec +17 -0
- data/lib/flipper.rb +33 -0
- data/lib/flipper/adapters/memory.rb +35 -0
- data/lib/flipper/dsl.rb +61 -0
- data/lib/flipper/errors.rb +11 -0
- data/lib/flipper/feature.rb +49 -0
- data/lib/flipper/gate.rb +55 -0
- data/lib/flipper/gates/actor.rb +29 -0
- data/lib/flipper/gates/boolean.rb +29 -0
- data/lib/flipper/gates/group.rb +32 -0
- data/lib/flipper/gates/percentage_of_actors.rb +25 -0
- data/lib/flipper/gates/percentage_of_random.rb +25 -0
- data/lib/flipper/registry.rb +36 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +78 -0
- data/lib/flipper/toggle.rb +31 -0
- data/lib/flipper/toggles/boolean.rb +22 -0
- data/lib/flipper/toggles/set.rb +17 -0
- data/lib/flipper/toggles/value.rb +17 -0
- data/lib/flipper/type.rb +18 -0
- data/lib/flipper/types/actor.rb +17 -0
- data/lib/flipper/types/boolean.rb +13 -0
- data/lib/flipper/types/group.rb +22 -0
- data/lib/flipper/types/percentage.rb +19 -0
- data/lib/flipper/types/percentage_of_actors.rb +6 -0
- data/lib/flipper/types/percentage_of_random.rb +6 -0
- data/lib/flipper/version.rb +3 -0
- data/spec/flipper/adapters/memory_spec.rb +19 -0
- data/spec/flipper/dsl_spec.rb +185 -0
- data/spec/flipper/feature_spec.rb +401 -0
- data/spec/flipper/registry_spec.rb +71 -0
- data/spec/flipper/types/actor_spec.rb +23 -0
- data/spec/flipper/types/boolean_spec.rb +9 -0
- data/spec/flipper/types/group_spec.rb +32 -0
- data/spec/flipper/types/percentage_of_actors_spec.rb +6 -0
- data/spec/flipper/types/percentage_of_random_spec.rb +6 -0
- data/spec/flipper/types/percentage_spec.rb +6 -0
- data/spec/flipper_spec.rb +59 -0
- data/spec/helper.rb +47 -0
- 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
|