flipper 0.26.2 → 0.28.3

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +61 -0
  3. data/Gemfile +2 -3
  4. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  5. data/examples/api/basic.ru +3 -4
  6. data/examples/api/custom_memoized.ru +3 -4
  7. data/examples/api/memoized.ru +3 -4
  8. data/examples/dsl.rb +3 -3
  9. data/examples/enabled_for_actor.rb +4 -2
  10. data/examples/mirroring.rb +59 -0
  11. data/lib/flipper/adapter.rb +23 -7
  12. data/lib/flipper/adapters/http.rb +11 -3
  13. data/lib/flipper/adapters/instrumented.rb +25 -2
  14. data/lib/flipper/adapters/memoizable.rb +19 -2
  15. data/lib/flipper/adapters/memory.rb +40 -16
  16. data/lib/flipper/adapters/operation_logger.rb +16 -3
  17. data/lib/flipper/dsl.rb +5 -9
  18. data/lib/flipper/errors.rb +3 -3
  19. data/lib/flipper/export.rb +26 -0
  20. data/lib/flipper/exporter.rb +17 -0
  21. data/lib/flipper/exporters/json/export.rb +32 -0
  22. data/lib/flipper/exporters/json/v1.rb +33 -0
  23. data/lib/flipper/feature.rb +12 -10
  24. data/lib/flipper/feature_check_context.rb +8 -4
  25. data/lib/flipper/gate.rb +12 -11
  26. data/lib/flipper/gates/actor.rb +11 -8
  27. data/lib/flipper/gates/group.rb +4 -2
  28. data/lib/flipper/gates/percentage_of_actors.rb +4 -5
  29. data/lib/flipper/identifier.rb +2 -2
  30. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  31. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  32. data/lib/flipper/poller.rb +1 -1
  33. data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
  34. data/lib/flipper/test/shared_adapter_test.rb +24 -0
  35. data/lib/flipper/typecast.rb +17 -0
  36. data/lib/flipper/types/actor.rb +13 -13
  37. data/lib/flipper/types/group.rb +4 -4
  38. data/lib/flipper/version.rb +1 -1
  39. data/lib/flipper.rb +5 -4
  40. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  41. data/spec/flipper/adapter_spec.rb +29 -2
  42. data/spec/flipper/adapters/http_spec.rb +25 -3
  43. data/spec/flipper/adapters/instrumented_spec.rb +28 -10
  44. data/spec/flipper/adapters/memoizable_spec.rb +30 -10
  45. data/spec/flipper/adapters/memory_spec.rb +11 -2
  46. data/spec/flipper/adapters/operation_logger_spec.rb +29 -10
  47. data/spec/flipper/dsl_spec.rb +25 -8
  48. data/spec/flipper/export_spec.rb +13 -0
  49. data/spec/flipper/exporter_spec.rb +16 -0
  50. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  51. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  52. data/spec/flipper/feature_check_context_spec.rb +5 -5
  53. data/spec/flipper/feature_spec.rb +76 -32
  54. data/spec/flipper/gates/boolean_spec.rb +1 -1
  55. data/spec/flipper/gates/group_spec.rb +2 -3
  56. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  57. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  58. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
  59. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -0
  60. data/spec/flipper/typecast_spec.rb +79 -0
  61. data/spec/flipper/types/actor_spec.rb +45 -45
  62. data/spec/flipper/types/group_spec.rb +2 -2
  63. data/spec/flipper_integration_spec.rb +62 -50
  64. data/spec/flipper_spec.rb +7 -1
  65. data/spec/support/skippable.rb +18 -0
  66. metadata +20 -2
@@ -30,9 +30,9 @@ RSpec.describe Flipper::Adapter do
30
30
  end
31
31
 
32
32
  describe '#import' do
33
- it 'returns nothing' do
33
+ it 'returns true' do
34
34
  result = destination_flipper.import(source_flipper)
35
- expect(result).to be(nil)
35
+ expect(result).to be(true)
36
36
  end
37
37
 
38
38
  it 'can import from one adapter to another' do
@@ -114,5 +114,32 @@ RSpec.describe Flipper::Adapter do
114
114
  destination_flipper.import(source_flipper)
115
115
  expect(destination_flipper.features.map(&:key)).to eq([])
116
116
  end
117
+
118
+ it 'can import an export' do
119
+ source_flipper.enable(:search)
120
+ source_flipper.enable(:google_analytics, Flipper::Actor.new("User;1"))
121
+
122
+ destination_flipper.import(source_flipper.export)
123
+
124
+ feature = destination_flipper[:search]
125
+ expect(feature.boolean_value).to be(true)
126
+
127
+ feature = destination_flipper[:google_analytics]
128
+ expect(feature.actors_value).to eq(Set["User;1"])
129
+ end
130
+ end
131
+
132
+ describe "#export" do
133
+ it "exports features" do
134
+ source_flipper.enable(:search)
135
+ export = source_flipper.export
136
+ expect(export.features.dig("search", :boolean)).to eq("true")
137
+ end
138
+
139
+ it "exports with arguments" do
140
+ source_flipper.enable(:search)
141
+ export = source_flipper.export(format: :json, version: 1)
142
+ expect(export.features.dig("search", :boolean)).to eq("true")
143
+ end
117
144
  end
118
145
  end
@@ -52,6 +52,28 @@ RSpec.describe Flipper::Adapters::Http do
52
52
  expect(flipper[:search].disable_group(:some_made_up_group)).to be(true)
53
53
  expect(flipper[:search].groups_value).to eq(Set.new)
54
54
  end
55
+
56
+ it "can import" do
57
+ adapter = Flipper::Adapters::Memory.new
58
+ source_flipper = Flipper.new(adapter)
59
+ source_flipper.enable_percentage_of_actors :search, 10
60
+ source_flipper.enable_percentage_of_time :search, 15
61
+ source_flipper.enable_actor :search, Flipper::Actor.new('User;1')
62
+ source_flipper.enable_actor :search, Flipper::Actor.new('User;100')
63
+ source_flipper.enable_group :search, :admins
64
+ source_flipper.enable_group :search, :employees
65
+ source_flipper.enable :plausible
66
+ source_flipper.disable :google_analytics
67
+
68
+ flipper = Flipper.new(subject)
69
+ flipper.import(source_flipper)
70
+ expect(flipper[:search].percentage_of_actors_value).to be(10)
71
+ expect(flipper[:search].percentage_of_time_value).to be(15)
72
+ expect(flipper[:search].actors_value).to eq(Set["User;1", "User;100"])
73
+ expect(flipper[:search].groups_value).to eq(Set["admins", "employees"])
74
+ expect(flipper[:plausible].boolean_value).to be(true)
75
+ expect(flipper[:google_analytics].boolean_value).to be(false)
76
+ end
55
77
  end
56
78
 
57
79
  it "sends default headers" do
@@ -82,7 +104,7 @@ RSpec.describe Flipper::Adapters::Http do
82
104
 
83
105
  describe "#get_multi" do
84
106
  it "raises error when not successful response" do
85
- stub_request(:get, "http://app.com/flipper/features?keys=feature_panel")
107
+ stub_request(:get, "http://app.com/flipper/features?keys=feature_panel&exclude_gate_names=true")
86
108
  .to_return(status: 503, body: "", headers: {})
87
109
 
88
110
  adapter = described_class.new(url: 'http://app.com/flipper')
@@ -94,7 +116,7 @@ RSpec.describe Flipper::Adapters::Http do
94
116
 
95
117
  describe "#get_all" do
96
118
  it "raises error when not successful response" do
97
- stub_request(:get, "http://app.com/flipper/features")
119
+ stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
98
120
  .to_return(status: 503, body: "", headers: {})
99
121
 
100
122
  adapter = described_class.new(url: 'http://app.com/flipper')
@@ -106,7 +128,7 @@ RSpec.describe Flipper::Adapters::Http do
106
128
 
107
129
  describe "#features" do
108
130
  it "raises error when not successful response" do
109
- stub_request(:get, "http://app.com/flipper/features")
131
+ stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
110
132
  .to_return(status: 503, body: "", headers: {})
111
133
 
112
134
  adapter = described_class.new(url: 'http://app.com/flipper')
@@ -16,16 +16,6 @@ RSpec.describe Flipper::Adapters::Instrumented do
16
16
 
17
17
  it_should_behave_like 'a flipper adapter'
18
18
 
19
- it 'forwards missing methods to underlying adapter' do
20
- adapter = Class.new do
21
- def foo
22
- :foo
23
- end
24
- end.new
25
- instrumented = described_class.new(adapter)
26
- expect(instrumented.foo).to eq(:foo)
27
- end
28
-
29
19
  describe '#name' do
30
20
  it 'is instrumented' do
31
21
  expect(subject.name).to be(:instrumented)
@@ -146,4 +136,32 @@ RSpec.describe Flipper::Adapters::Instrumented do
146
136
  expect(event.payload[:result]).to be(result)
147
137
  end
148
138
  end
139
+
140
+ describe '#import' do
141
+ it 'records instrumentation' do
142
+ result = subject.import(Flipper::Adapters::Memory.new)
143
+
144
+ event = instrumenter.events.last
145
+ expect(event).not_to be_nil
146
+ expect(event.name).to eq('adapter_operation.flipper')
147
+ expect(event.payload[:operation]).to eq(:import)
148
+ expect(event.payload[:adapter_name]).to eq(:memory)
149
+ expect(event.payload[:result]).to be(result)
150
+ end
151
+ end
152
+
153
+ describe '#export' do
154
+ it 'records instrumentation' do
155
+ result = subject.export(format: :json, version: 1)
156
+
157
+ event = instrumenter.events.last
158
+ expect(event).not_to be_nil
159
+ expect(event.name).to eq('adapter_operation.flipper')
160
+ expect(event.payload[:operation]).to eq(:export)
161
+ expect(event.payload[:adapter_name]).to eq(:memory)
162
+ expect(event.payload[:format]).to be(:json)
163
+ expect(event.payload[:version]).to be(1)
164
+ expect(event.payload[:result]).to be(result)
165
+ end
166
+ end
149
167
  end
@@ -11,16 +11,6 @@ RSpec.describe Flipper::Adapters::Memoizable do
11
11
 
12
12
  it_should_behave_like 'a flipper adapter'
13
13
 
14
- it 'forwards missing methods to underlying adapter' do
15
- adapter = Class.new do
16
- def foo
17
- :foo
18
- end
19
- end.new
20
- memoizable = described_class.new(adapter)
21
- expect(memoizable.foo).to eq(:foo)
22
- end
23
-
24
14
  describe '#name' do
25
15
  it 'is instrumented' do
26
16
  expect(subject.name).to be(:memoizable)
@@ -249,6 +239,36 @@ RSpec.describe Flipper::Adapters::Memoizable do
249
239
  end
250
240
  end
251
241
 
242
+ describe "#import" do
243
+ context "with memoization enabled" do
244
+ before do
245
+ subject.memoize = true
246
+ end
247
+
248
+ it "unmemoizes features" do
249
+ cache[:foo] = "bar"
250
+ flipper[:stats].enable
251
+ flipper[:search].disable
252
+ subject.import(Flipper::Adapters::Memory.new)
253
+ expect(cache).to be_empty
254
+ end
255
+ end
256
+
257
+ context "with memoization disabled" do
258
+ before do
259
+ subject.memoize = false
260
+ end
261
+
262
+ it "does not unmemoize features" do
263
+ cache[:foo] = "bar"
264
+ flipper[:stats].enable
265
+ flipper[:search].disable
266
+ subject.import(Flipper::Adapters::Memory.new)
267
+ expect(cache).not_to be_empty
268
+ end
269
+ end
270
+ end
271
+
252
272
  describe '#features' do
253
273
  context 'with memoization enabled' do
254
274
  before do
@@ -1,8 +1,17 @@
1
1
  RSpec.describe Flipper::Adapters::Memory do
2
2
  let(:source) { {} }
3
- subject { described_class.new(source) }
4
3
 
5
- it_should_behave_like 'a flipper adapter'
4
+ context 'threadsafe: true' do
5
+ subject { described_class.new(source, threadsafe: true) }
6
+
7
+ it_should_behave_like 'a flipper adapter'
8
+ end
9
+
10
+ context 'threadsafe: false' do
11
+ subject { described_class.new(source, threadsafe: false) }
12
+
13
+ it_should_behave_like 'a flipper adapter'
14
+ end
6
15
 
7
16
  it "can initialize from big hash" do
8
17
  flipper = Flipper.new(subject)
@@ -18,16 +18,6 @@ RSpec.describe Flipper::Adapters::OperationLogger do
18
18
  expect(output).to match(/@adapter=#<Flipper::Adapters::Memory/)
19
19
  end
20
20
 
21
- it 'forwards missing methods to underlying adapter' do
22
- adapter = Class.new do
23
- def foo
24
- :foo
25
- end
26
- end.new
27
- operation_logger = described_class.new(adapter)
28
- expect(operation_logger.foo).to eq(:foo)
29
- end
30
-
31
21
  describe '#get' do
32
22
  before do
33
23
  @feature = flipper[:stats]
@@ -106,4 +96,33 @@ RSpec.describe Flipper::Adapters::OperationLogger do
106
96
  expect(@result).to eq(adapter.add(@feature))
107
97
  end
108
98
  end
99
+
100
+ describe '#import' do
101
+ before do
102
+ @source = Flipper::Adapters::Memory.new
103
+ @result = subject.import(@source)
104
+ end
105
+
106
+ it 'logs operation' do
107
+ expect(subject.count(:import)).to be(1)
108
+ end
109
+
110
+ it 'returns result' do
111
+ expect(@result).to eq(adapter.import(@source))
112
+ end
113
+ end
114
+
115
+ describe '#export' do
116
+ before do
117
+ @result = subject.export(format: :json, version: 1)
118
+ end
119
+
120
+ it 'logs operation' do
121
+ expect(subject.count(:export)).to be(1)
122
+ end
123
+
124
+ it 'returns result' do
125
+ expect(@result).to eq(adapter.export(format: :json, version: 1))
126
+ end
127
+ end
109
128
  end
@@ -149,12 +149,12 @@ RSpec.describe Flipper::DSL do
149
149
  end
150
150
 
151
151
  describe '#actor' do
152
- context 'for a thing' do
152
+ context 'for an actor' do
153
153
  it 'returns actor instance' do
154
- thing = Flipper::Actor.new(33)
155
- actor = subject.actor(thing)
156
- expect(actor).to be_instance_of(Flipper::Types::Actor)
157
- expect(actor.value).to eq('33')
154
+ actor = Flipper::Actor.new(33)
155
+ flipper_actor = subject.actor(actor)
156
+ expect(flipper_actor).to be_instance_of(Flipper::Types::Actor)
157
+ expect(flipper_actor.value).to eq('33')
158
158
  end
159
159
  end
160
160
 
@@ -342,10 +342,27 @@ RSpec.describe Flipper::DSL do
342
342
  end
343
343
 
344
344
  describe '#import' do
345
+ context "with flipper instance" do
346
+ it 'delegates to adapter' do
347
+ destination_flipper = build_flipper
348
+ expect(subject.adapter).to receive(:import).with(destination_flipper)
349
+ subject.import(destination_flipper)
350
+ end
351
+ end
352
+
353
+ context "with flipper adapter" do
354
+ it 'delegates to adapter' do
355
+ destination_flipper = build_flipper
356
+ expect(subject.adapter).to receive(:import).with(destination_flipper.adapter)
357
+ subject.import(destination_flipper.adapter)
358
+ end
359
+ end
360
+ end
361
+
362
+ describe "#export" do
345
363
  it 'delegates to adapter' do
346
- destination_flipper = build_flipper
347
- expect(subject.adapter).to receive(:import).with(destination_flipper.adapter)
348
- subject.import(destination_flipper)
364
+ expect(subject.export).to eq(subject.adapter.export)
365
+ expect(subject.export(format: :json)).to eq(subject.adapter.export(format: :json))
349
366
  end
350
367
  end
351
368
 
@@ -0,0 +1,13 @@
1
+ RSpec.describe Flipper::Export do
2
+ it "can initialize" do
3
+ export = described_class.new(contents: "{}", format: :json, version: 1)
4
+ expect(export.contents).to eq("{}")
5
+ expect(export.format).to eq(:json)
6
+ expect(export.version).to eq(1)
7
+ end
8
+
9
+ it "raises not implemented for features" do
10
+ export = described_class.new(contents: "{}", format: :json, version: 1)
11
+ expect { export.features }.to raise_error(NotImplementedError)
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ RSpec.describe Flipper::Exporter do
2
+ describe ".build" do
3
+ it "builds instance of exporter" do
4
+ exporter = described_class.build(format: :json, version: 1)
5
+ expect(exporter).to be_instance_of(Flipper::Exporters::Json::V1)
6
+ end
7
+
8
+ it "raises if format not found" do
9
+ expect { described_class.build(format: :nope, version: 1) }.to raise_error(KeyError)
10
+ end
11
+
12
+ it "raises if version not found" do
13
+ expect { described_class.build(format: :json, version: 0) }.to raise_error(KeyError)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ require 'flipper/exporters/json/v1'
2
+
3
+ RSpec.describe Flipper::Exporters::Json::Export do
4
+ let(:contents) {
5
+ <<~JSON
6
+ {
7
+ "version":1,
8
+ "features":{
9
+ "search":{"boolean":null,"groups":["admins","employees"],"actors":["User;1","User;100"],"percentage_of_actors":"10","percentage_of_time":"15"},
10
+ "plausible":{"boolean":"true","groups":[],"actors":[],"percentage_of_actors":null,"percentage_of_time":null},
11
+ "google_analytics":{"boolean":null,"groups":[],"actors":[],"percentage_of_actors":null,"percentage_of_time":null}
12
+ }
13
+ }
14
+ JSON
15
+ }
16
+
17
+ it "can initialize" do
18
+ export = described_class.new(contents: contents)
19
+ expect(export.format).to eq(:json)
20
+ expect(export.version).to be(1)
21
+ end
22
+
23
+ it "can initialize with version" do
24
+ export = described_class.new(contents: contents, version: 1)
25
+ expect(export.version).to be(1)
26
+ end
27
+
28
+ it "can build features from contents" do
29
+ export = Flipper::Exporters::Json::Export.new(contents: contents)
30
+ expect(export.features).to eq({
31
+ "search" => {actors: Set["User;1", "User;100"], boolean: nil, groups: Set["admins", "employees"], percentage_of_actors: "10", percentage_of_time: "15"},
32
+ "plausible" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
33
+ "google_analytics" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
34
+ })
35
+ end
36
+
37
+ it "can build an adapter from features" do
38
+ export = Flipper::Exporters::Json::Export.new(contents: contents)
39
+ expect(export.adapter).to be_instance_of(Flipper::Adapters::Memory)
40
+ expect(export.adapter.get_all).to eq({
41
+ "plausible" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
42
+ "search" => {actors: Set["User;1", "User;100"], boolean: nil, groups: Set["admins", "employees"], percentage_of_actors: "10", percentage_of_time: "15"},
43
+ "google_analytics" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
44
+ })
45
+ end
46
+
47
+ it "raises for invalid json" do
48
+ export = described_class.new(contents: "bad contents")
49
+ expect {
50
+ export.features
51
+ }.to raise_error(Flipper::Exporters::Json::JsonError)
52
+ end
53
+
54
+ it "raises for missing features key" do
55
+ export = described_class.new(contents: "{}")
56
+ expect {
57
+ export.features
58
+ }.to raise_error(Flipper::Exporters::Json::InvalidError)
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ require 'flipper/exporters/json/v1'
2
+
3
+ RSpec.describe Flipper::Exporters::Json::V1 do
4
+ subject { described_class.new }
5
+
6
+ it "has a version number" do
7
+ adapter = Flipper::Adapters::Memory.new
8
+ export = subject.call(adapter)
9
+ data = JSON.parse(export.contents)
10
+ expect(data["version"]).to eq(1)
11
+ end
12
+
13
+ it "exports features and gates" do
14
+ adapter = Flipper::Adapters::Memory.new
15
+ flipper = Flipper.new(adapter)
16
+ flipper.enable_percentage_of_actors :search, 10
17
+ flipper.enable_percentage_of_time :search, 15
18
+ flipper.enable_actor :search, Flipper::Actor.new('User;1')
19
+ flipper.enable_actor :search, Flipper::Actor.new('User;100')
20
+ flipper.enable_group :search, :admins
21
+ flipper.enable_group :search, :employees
22
+ flipper.enable :plausible
23
+ flipper.disable :google_analytics
24
+
25
+ export = subject.call(adapter)
26
+
27
+ expect(export.features).to eq({
28
+ "google_analytics" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
29
+ "plausible" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
30
+ "search" => {actors: Set["User;1", "User;100"], boolean: nil, groups: Set["admins", "employees"], percentage_of_actors: "10", percentage_of_time: "15"},
31
+ })
32
+ end
33
+ end
@@ -1,12 +1,12 @@
1
1
  RSpec.describe Flipper::FeatureCheckContext do
2
2
  let(:feature_name) { :new_profiles }
3
3
  let(:values) { Flipper::GateValues.new({}) }
4
- let(:thing) { Flipper::Actor.new('5') }
4
+ let(:actor) { Flipper::Actor.new('5') }
5
5
  let(:options) do
6
6
  {
7
7
  feature_name: feature_name,
8
8
  values: values,
9
- thing: thing,
9
+ actors: [actor],
10
10
  }
11
11
  end
12
12
 
@@ -14,7 +14,7 @@ RSpec.describe Flipper::FeatureCheckContext do
14
14
  instance = described_class.new(**options)
15
15
  expect(instance.feature_name).to eq(feature_name)
16
16
  expect(instance.values).to eq(values)
17
- expect(instance.thing).to eq(thing)
17
+ expect(instance.actors).to eq([actor])
18
18
  end
19
19
 
20
20
  it 'requires feature_name' do
@@ -31,8 +31,8 @@ RSpec.describe Flipper::FeatureCheckContext do
31
31
  end.to raise_error(ArgumentError)
32
32
  end
33
33
 
34
- it 'requires thing' do
35
- options.delete(:thing)
34
+ it 'requires actors' do
35
+ options.delete(:actors)
36
36
  expect do
37
37
  described_class.new(**options)
38
38
  end.to raise_error(ArgumentError)
@@ -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