flipper 0.27.1 → 0.28.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.
@@ -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)