flipper 0.27.1 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,6 +32,52 @@ RSpec.describe Flipper::Feature do
32
32
  end
33
33
  end
34
34
 
35
+ describe "#enabled?" do
36
+ context "for an actor" do
37
+ let(:actor) { Flipper::Actor.new("User;1") }
38
+
39
+ it 'returns true if feature is enabled' do
40
+ subject.enable
41
+ expect(subject.enabled?(actor)).to be(true)
42
+ end
43
+
44
+ it 'returns false if feature is disabled' do
45
+ subject.disable
46
+ expect(subject.enabled?(actor)).to be(false)
47
+ end
48
+ end
49
+
50
+ context "for multiple actors" do
51
+ let(:actors) {
52
+ [
53
+ Flipper::Actor.new("User;1"),
54
+ Flipper::Actor.new("User;2"),
55
+ Flipper::Actor.new("User;3"),
56
+ ]
57
+ }
58
+
59
+ it 'returns true if feature is enabled' do
60
+ subject.enable
61
+ expect(subject.enabled?(actors)).to be(true)
62
+ end
63
+
64
+ it 'returns true if feature is enabled for any actor' do
65
+ subject.enable_actor actors.first
66
+ expect(subject.enabled?(actors)).to be(true)
67
+ end
68
+
69
+ it 'returns true if feature is enabled for any actor with multiple arguments' do
70
+ subject.enable_actor actors.last
71
+ expect(subject.enabled?(*actors)).to be(true)
72
+ end
73
+
74
+ it 'returns false if feature is disabled for all actors' do
75
+ subject.disable
76
+ expect(subject.enabled?(actors)).to be(false)
77
+ end
78
+ end
79
+ end
80
+
35
81
  describe '#to_s' do
36
82
  it 'returns name as string' do
37
83
  feature = described_class.new(:search, adapter)
@@ -148,29 +194,29 @@ RSpec.describe Flipper::Feature do
148
194
  end
149
195
 
150
196
  it 'is recorded for enable' do
151
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
152
- gate = subject.gate_for(thing)
197
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
198
+ gate = subject.gate_for(actor)
153
199
 
154
- subject.enable(thing)
200
+ subject.enable(actor)
155
201
 
156
202
  event = instrumenter.events.last
157
203
  expect(event).not_to be_nil
158
204
  expect(event.name).to eq('feature_operation.flipper')
159
205
  expect(event.payload[:feature_name]).to eq(:search)
160
206
  expect(event.payload[:operation]).to eq(:enable)
161
- expect(event.payload[:thing]).to eq(thing)
207
+ expect(event.payload[:thing]).to eq(actor)
162
208
  expect(event.payload[:result]).not_to be_nil
163
209
  end
164
210
 
165
211
  it 'always instruments flipper type instance for enable' do
166
- thing = Flipper::Actor.new('1')
167
- gate = subject.gate_for(thing)
212
+ actor = Flipper::Actor.new('1')
213
+ gate = subject.gate_for(actor)
168
214
 
169
- subject.enable(thing)
215
+ subject.enable(actor)
170
216
 
171
217
  event = instrumenter.events.last
172
218
  expect(event).not_to be_nil
173
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
219
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
174
220
  end
175
221
 
176
222
  it 'is recorded for disable' do
@@ -219,15 +265,15 @@ RSpec.describe Flipper::Feature do
219
265
  end
220
266
 
221
267
  it 'always instruments flipper type instance for disable' do
222
- thing = Flipper::Actor.new('1')
223
- gate = subject.gate_for(thing)
268
+ actor = Flipper::Actor.new('1')
269
+ gate = subject.gate_for(actor)
224
270
 
225
- subject.disable(thing)
271
+ subject.disable(actor)
226
272
 
227
273
  event = instrumenter.events.last
228
274
  expect(event).not_to be_nil
229
275
  expect(event.payload[:operation]).to eq(:disable)
230
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
276
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
231
277
  end
232
278
 
233
279
  it 'is recorded for add' do
@@ -275,17 +321,15 @@ RSpec.describe Flipper::Feature do
275
321
  end
276
322
 
277
323
  it 'is recorded for enabled?' do
278
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
279
- gate = subject.gate_for(thing)
280
-
281
- subject.enabled?(thing)
324
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
325
+ subject.enabled?(actor)
282
326
 
283
327
  event = instrumenter.events.last
284
328
  expect(event).not_to be_nil
285
329
  expect(event.name).to eq('feature_operation.flipper')
286
330
  expect(event.payload[:feature_name]).to eq(:search)
287
331
  expect(event.payload[:operation]).to eq(:enabled?)
288
- expect(event.payload[:thing]).to eq(thing)
332
+ expect(event.payload[:actors]).to eq([actor])
289
333
  expect(event.payload[:result]).to eq(false)
290
334
  end
291
335
 
@@ -293,8 +337,8 @@ RSpec.describe Flipper::Feature do
293
337
  actor = Flipper::Types::Actor.new(user)
294
338
  {
295
339
  nil => nil,
296
- user => actor,
297
- actor => actor,
340
+ user => [actor],
341
+ actor => [actor],
298
342
  }.each do |thing, wrapped_thing|
299
343
  it "always instruments #{thing.inspect} as #{wrapped_thing.class} for enabled?" do
300
344
  subject.enabled?(thing)
@@ -302,7 +346,7 @@ RSpec.describe Flipper::Feature do
302
346
  event = instrumenter.events.last
303
347
  expect(event).not_to be_nil
304
348
  expect(event.payload[:operation]).to eq(:enabled?)
305
- expect(event.payload[:thing]).to eq(wrapped_thing)
349
+ expect(event.payload[:actors]).to eq(wrapped_thing)
306
350
  end
307
351
  end
308
352
  end
@@ -428,10 +472,10 @@ RSpec.describe Flipper::Feature do
428
472
 
429
473
  context 'when one or more groups enabled' do
430
474
  before do
431
- @staff = Flipper.register(:staff) { |_thing| true }
432
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
433
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
434
- @disabled = Flipper.register(:disabled) { |_thing| true }
475
+ @staff = Flipper.register(:staff) { |actor| true }
476
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
477
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
478
+ @disabled = Flipper.register(:disabled) { |actor| true }
435
479
  subject.enable @staff
436
480
  subject.enable @preview_features
437
481
  subject.disable @disabled
@@ -467,10 +511,10 @@ RSpec.describe Flipper::Feature do
467
511
 
468
512
  context 'when one or more groups enabled' do
469
513
  before do
470
- @staff = Flipper.register(:staff) { |_thing| true }
471
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
472
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
473
- @disabled = Flipper.register(:disabled) { |_thing| true }
514
+ @staff = Flipper.register(:staff) { |actor| true }
515
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
516
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
517
+ @disabled = Flipper.register(:disabled) { |actor| true }
474
518
  subject.enable @staff
475
519
  subject.enable @preview_features
476
520
  subject.disable @disabled
@@ -494,10 +538,10 @@ RSpec.describe Flipper::Feature do
494
538
 
495
539
  context 'when one or more groups enabled' do
496
540
  before do
497
- @staff = Flipper.register(:staff) { |_thing| true }
498
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
499
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
500
- @disabled = Flipper.register(:disabled) { |_thing| true }
541
+ @staff = Flipper.register(:staff) { |actor| true }
542
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
543
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
544
+ @disabled = Flipper.register(:disabled) { |actor| true }
501
545
  subject.enable @staff
502
546
  subject.enable @preview_features
503
547
  subject.disable @disabled
@@ -9,7 +9,7 @@ RSpec.describe Flipper::Gates::Boolean do
9
9
  Flipper::FeatureCheckContext.new(
10
10
  feature_name: feature_name,
11
11
  values: Flipper::GateValues.new(boolean: bool),
12
- thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
12
+ actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
13
13
  )
14
14
  end
15
15
 
@@ -9,18 +9,17 @@ RSpec.describe Flipper::Gates::Group do
9
9
  Flipper::FeatureCheckContext.new(
10
10
  feature_name: feature_name,
11
11
  values: Flipper::GateValues.new(groups: set),
12
- thing: Flipper::Types::Actor.new(Flipper::Actor.new('5'))
12
+ actors: [Flipper::Types::Actor.new(Flipper::Actor.new('5'))]
13
13
  )
14
14
  end
15
15
 
16
16
  describe '#open?' do
17
17
  context 'with a group in adapter, but not registered' do
18
18
  before do
19
- Flipper.register(:staff) { |_thing| true }
19
+ Flipper.register(:staff) { |actor| true }
20
20
  end
21
21
 
22
22
  it 'ignores group' do
23
- thing = Flipper::Actor.new('5')
24
23
  expect(subject.open?(context(Set[:newbs, :staff]))).to be(true)
25
24
  end
26
25
  end
@@ -5,11 +5,11 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
5
5
  described_class.new
6
6
  end
7
7
 
8
- def context(percentage_of_actors_value, feature = feature_name, thing = nil)
8
+ def context(percentage_of_actors_value, feature = feature_name, actors = nil)
9
9
  Flipper::FeatureCheckContext.new(
10
10
  feature_name: feature,
11
11
  values: Flipper::GateValues.new(percentage_of_actors: percentage_of_actors_value),
12
- thing: Flipper::Types::Actor.new(thing || Flipper::Actor.new(1))
12
+ actors: Array(actors) || [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
13
13
  )
14
14
  end
15
15
 
@@ -20,7 +20,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
20
20
  let(:number_of_actors) { 10_000 }
21
21
 
22
22
  let(:actors) do
23
- (1..number_of_actors).map { |n| Flipper::Actor.new(n) }
23
+ (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new(n.to_s)) }
24
24
  end
25
25
 
26
26
  let(:feature_one_enabled_actors) do
@@ -48,13 +48,69 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
48
48
  end
49
49
  end
50
50
 
51
+ context "with an array of actors" do
52
+ let(:percentage) { 0.05 }
53
+ let(:percentage_as_integer) { percentage * 100 }
54
+ let(:number_of_actors) { 3_000 }
55
+
56
+ let(:user_actors) do
57
+ (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("User;#{n}")) }
58
+ end
59
+
60
+ let(:team_actors) do
61
+ (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("Team;#{n}")) }
62
+ end
63
+
64
+ let(:org_actors) do
65
+ (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("Org;#{n}")) }
66
+ end
67
+
68
+ let(:actors) { user_actors + team_actors + org_actors }
69
+
70
+ let(:feature_one_enabled_actors) do
71
+ actors.each_slice(3).select do |group|
72
+ context = context(percentage_as_integer, :name_one, group)
73
+ subject.open?(context)
74
+ end.flatten
75
+ end
76
+
77
+ let(:feature_two_enabled_actors) do
78
+ actors.each_slice(3).select do |group|
79
+ context = context(percentage_as_integer, :name_two, group)
80
+ subject.open?(context)
81
+ end.flatten
82
+ end
83
+
84
+ it 'does not enable both features for same set of actors' do
85
+ expect(feature_one_enabled_actors).not_to eq(feature_two_enabled_actors)
86
+ end
87
+
88
+ it 'enables feature for accurate number of actors for each feature' do
89
+ margin_of_error = 0.02 * actors.size # 2 percent margin of error
90
+ expected_enabled_size = actors.size * percentage
91
+
92
+ [
93
+ feature_one_enabled_actors.size,
94
+ feature_two_enabled_actors.size,
95
+ ].each do |size|
96
+ expect(size).to be_within(margin_of_error).of(expected_enabled_size)
97
+ end
98
+ end
99
+
100
+ it "is consistent regardless of order of actors" do
101
+ actors = user_actors.first(10)
102
+ results = 100.times.map { |n| subject.open?(context(75, :some_feature, actors.shuffle)) }
103
+ expect(results.uniq).to eq([true])
104
+ end
105
+ end
106
+
51
107
  context 'for fractional percentage' do
52
108
  let(:decimal) { 0.001 }
53
109
  let(:percentage) { decimal * 100 }
54
110
  let(:number_of_actors) { 10_000 }
55
111
 
56
112
  let(:actors) do
57
- (1..number_of_actors).map { |n| Flipper::Actor.new(n) }
113
+ (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new(n.to_s)) }
58
114
  end
59
115
 
60
116
  subject { described_class.new }
@@ -64,7 +120,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
64
120
  expected_open_count = number_of_actors * decimal
65
121
 
66
122
  open_count = actors.select do |actor|
67
- context = context(percentage, :feature, actor)
123
+ context = context(percentage, :feature, [actor])
68
124
  subject.open?(context)
69
125
  end.size
70
126
 
@@ -5,11 +5,11 @@ RSpec.describe Flipper::Gates::PercentageOfTime do
5
5
  described_class.new
6
6
  end
7
7
 
8
- def context(percentage_of_time_value, feature = feature_name, thing = nil)
8
+ def context(percentage_of_time_value, feature = feature_name, actors = nil)
9
9
  Flipper::FeatureCheckContext.new(
10
10
  feature_name: feature,
11
11
  values: Flipper::GateValues.new(percentage_of_time: percentage_of_time_value),
12
- thing: thing || Flipper::Types::Actor.new(Flipper::Actor.new(1))
12
+ actors: Array(actors) || [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
13
13
  )
14
14
  end
15
15
 
@@ -18,8 +18,8 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
18
18
  end
19
19
 
20
20
  before do
21
- Flipper.register(:admins) do |thing|
22
- thing.respond_to?(:admin?) && thing.admin?
21
+ Flipper.register(:admins) do |actor|
22
+ actor.respond_to?(:admin?) && actor.admin?
23
23
  end
24
24
 
25
25
  @io = StringIO.new
@@ -46,7 +46,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
46
46
 
47
47
  it 'logs feature calls with result after operation' do
48
48
  feature_line = find_line('Flipper feature(search) enabled? false')
49
- expect(feature_line).to include('[ thing=nil ]')
49
+ expect(feature_line).to include('[ actors=nil ]')
50
50
  end
51
51
 
52
52
  it 'logs adapter calls' do
@@ -56,7 +56,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
56
56
  end
57
57
  end
58
58
 
59
- context 'feature enabled checks with a thing' do
59
+ context 'feature enabled checks with an actor' do
60
60
  let(:user) { Flipper::Types::Actor.new(Flipper::Actor.new('1')) }
61
61
 
62
62
  before do
@@ -64,7 +64,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
64
64
  flipper[:search].enabled?(user)
65
65
  end
66
66
 
67
- it 'logs thing for feature' do
67
+ it 'logs actors for feature' do
68
68
  feature_line = find_line('Flipper feature(search) enabled?')
69
69
  expect(feature_line).to include(user.inspect)
70
70
  end
@@ -2,11 +2,11 @@ require 'flipper/types/actor'
2
2
 
3
3
  RSpec.describe Flipper::Types::Actor do
4
4
  subject do
5
- thing = thing_class.new('2')
6
- described_class.new(thing)
5
+ actor = actor_class.new('2')
6
+ described_class.new(actor)
7
7
  end
8
8
 
9
- let(:thing_class) do
9
+ let(:actor_class) do
10
10
  Class.new do
11
11
  attr_reader :flipper_id
12
12
 
@@ -22,14 +22,14 @@ RSpec.describe Flipper::Types::Actor do
22
22
 
23
23
  describe '.wrappable?' do
24
24
  it 'returns true if actor' do
25
- thing = thing_class.new('1')
26
- actor = described_class.new(thing)
27
- expect(described_class.wrappable?(actor)).to eq(true)
25
+ actor = actor_class.new('1')
26
+ actor_type_instance = described_class.new(actor)
27
+ expect(described_class.wrappable?(actor_type_instance)).to eq(true)
28
28
  end
29
29
 
30
30
  it 'returns true if responds to flipper_id' do
31
- thing = thing_class.new(10)
32
- expect(described_class.wrappable?(thing)).to eq(true)
31
+ actor = actor_class.new(10)
32
+ expect(described_class.wrappable?(actor)).to eq(true)
33
33
  end
34
34
 
35
35
  it 'returns false if nil' do
@@ -38,27 +38,27 @@ RSpec.describe Flipper::Types::Actor do
38
38
  end
39
39
 
40
40
  describe '.wrap' do
41
- context 'for actor' do
42
- it 'returns actor' do
43
- actor = described_class.wrap(subject)
44
- expect(actor).to be_instance_of(described_class)
45
- expect(actor).to be(subject)
41
+ context 'for actor type instance' do
42
+ it 'returns actor type instance' do
43
+ actor_type_instance = described_class.wrap(subject)
44
+ expect(actor_type_instance).to be_instance_of(described_class)
45
+ expect(actor_type_instance).to be(subject)
46
46
  end
47
47
  end
48
48
 
49
- context 'for other thing' do
50
- it 'returns actor' do
51
- thing = thing_class.new('1')
52
- actor = described_class.wrap(thing)
53
- expect(actor).to be_instance_of(described_class)
49
+ context 'for other object' do
50
+ it 'returns actor type instance' do
51
+ actor = actor_class.new('1')
52
+ actor_type_instance = described_class.wrap(actor)
53
+ expect(actor_type_instance).to be_instance_of(described_class)
54
54
  end
55
55
  end
56
56
  end
57
57
 
58
- it 'initializes with thing that responds to id' do
59
- thing = thing_class.new('1')
60
- actor = described_class.new(thing)
61
- expect(actor.value).to eq('1')
58
+ it 'initializes with object that responds to flipper_id' do
59
+ actor = actor_class.new('1')
60
+ actor_type_instance = described_class.new(actor)
61
+ expect(actor_type_instance.value).to eq('1')
62
62
  end
63
63
 
64
64
  it 'raises error when initialized with nil' do
@@ -68,48 +68,48 @@ RSpec.describe Flipper::Types::Actor do
68
68
  end
69
69
 
70
70
  it 'raises error when initalized with non-wrappable object' do
71
- unwrappable_thing = Struct.new(:id).new(1)
71
+ unwrappable_object = Struct.new(:id).new(1)
72
72
  expect do
73
- described_class.new(unwrappable_thing)
73
+ described_class.new(unwrappable_object)
74
74
  end.to raise_error(ArgumentError,
75
- "#{unwrappable_thing.inspect} must respond to flipper_id, but does not")
75
+ "#{unwrappable_object.inspect} must respond to flipper_id, but does not")
76
76
  end
77
77
 
78
78
  it 'converts id to string' do
79
- thing = thing_class.new(2)
80
- actor = described_class.new(thing)
79
+ actor = actor_class.new(2)
80
+ actor = described_class.new(actor)
81
81
  expect(actor.value).to eq('2')
82
82
  end
83
83
 
84
- it 'proxies everything to thing' do
85
- thing = thing_class.new(10)
86
- actor = described_class.new(thing)
84
+ it 'proxies everything to actor' do
85
+ actor = actor_class.new(10)
86
+ actor = described_class.new(actor)
87
87
  expect(actor.admin?).to eq(true)
88
88
  end
89
89
 
90
- it 'exposes thing' do
91
- thing = thing_class.new(10)
92
- actor = described_class.new(thing)
93
- expect(actor.thing).to be(thing)
90
+ it 'exposes actor' do
91
+ actor = actor_class.new(10)
92
+ actor_type_instance = described_class.new(actor)
93
+ expect(actor_type_instance.actor).to be(actor)
94
94
  end
95
95
 
96
96
  describe '#respond_to?' do
97
97
  it 'returns true if responds to method' do
98
- thing = thing_class.new('1')
99
- actor = described_class.new(thing)
100
- expect(actor.respond_to?(:value)).to eq(true)
98
+ actor = actor_class.new('1')
99
+ actor_type_instance = described_class.new(actor)
100
+ expect(actor_type_instance.respond_to?(:value)).to eq(true)
101
101
  end
102
102
 
103
- it 'returns true if thing responds to method' do
104
- thing = thing_class.new(10)
105
- actor = described_class.new(thing)
106
- expect(actor.respond_to?(:admin?)).to eq(true)
103
+ it 'returns true if actor responds to method' do
104
+ actor = actor_class.new(10)
105
+ actor_type_instance = described_class.new(actor)
106
+ expect(actor_type_instance.respond_to?(:admin?)).to eq(true)
107
107
  end
108
108
 
109
- it 'returns false if does not respond to method and thing does not respond to method' do
110
- thing = thing_class.new(10)
111
- actor = described_class.new(thing)
112
- expect(actor.respond_to?(:frankenstein)).to eq(false)
109
+ it 'returns false if does not respond to method and actor does not respond to method' do
110
+ actor = actor_class.new(10)
111
+ actor_type_instance = described_class.new(actor)
112
+ expect(actor_type_instance.respond_to?(:frankenstein)).to eq(false)
113
113
  end
114
114
  end
115
115
  end
@@ -90,7 +90,7 @@ RSpec.describe Flipper::Types::Group do
90
90
  context = Flipper::FeatureCheckContext.new(
91
91
  feature_name: :my_feature,
92
92
  values: Flipper::GateValues.new({}),
93
- thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
93
+ actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
94
94
  )
95
95
  group = Flipper.register(:group_with_context) { |actor| actor }
96
96
  yielded_actor = group.match?(admin_actor, context)
@@ -101,7 +101,7 @@ RSpec.describe Flipper::Types::Group do
101
101
  context = Flipper::FeatureCheckContext.new(
102
102
  feature_name: :my_feature,
103
103
  values: Flipper::GateValues.new({}),
104
- thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
104
+ actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
105
105
  )
106
106
  group = Flipper.register(:group_with_context) { |actor, context| [actor, context] }
107
107
  yielded_actor, yielded_context = group.match?(admin_actor, context)