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.
- checksums.yaml +4 -4
- data/Changelog.md +61 -0
- data/Gemfile +2 -3
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- data/examples/dsl.rb +3 -3
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/mirroring.rb +59 -0
- data/lib/flipper/adapter.rb +23 -7
- data/lib/flipper/adapters/http.rb +11 -3
- data/lib/flipper/adapters/instrumented.rb +25 -2
- data/lib/flipper/adapters/memoizable.rb +19 -2
- data/lib/flipper/adapters/memory.rb +40 -16
- data/lib/flipper/adapters/operation_logger.rb +16 -3
- data/lib/flipper/dsl.rb +5 -9
- data/lib/flipper/errors.rb +3 -3
- data/lib/flipper/export.rb +26 -0
- data/lib/flipper/exporter.rb +17 -0
- data/lib/flipper/exporters/json/export.rb +32 -0
- data/lib/flipper/exporters/json/v1.rb +33 -0
- data/lib/flipper/feature.rb +12 -10
- data/lib/flipper/feature_check_context.rb +8 -4
- data/lib/flipper/gate.rb +12 -11
- data/lib/flipper/gates/actor.rb +11 -8
- data/lib/flipper/gates/group.rb +4 -2
- data/lib/flipper/gates/percentage_of_actors.rb +4 -5
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/poller.rb +1 -1
- data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
- data/lib/flipper/test/shared_adapter_test.rb +24 -0
- data/lib/flipper/typecast.rb +17 -0
- data/lib/flipper/types/actor.rb +13 -13
- data/lib/flipper/types/group.rb +4 -4
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +5 -4
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/adapter_spec.rb +29 -2
- data/spec/flipper/adapters/http_spec.rb +25 -3
- data/spec/flipper/adapters/instrumented_spec.rb +28 -10
- data/spec/flipper/adapters/memoizable_spec.rb +30 -10
- data/spec/flipper/adapters/memory_spec.rb +11 -2
- data/spec/flipper/adapters/operation_logger_spec.rb +29 -10
- data/spec/flipper/dsl_spec.rb +25 -8
- data/spec/flipper/export_spec.rb +13 -0
- data/spec/flipper/exporter_spec.rb +16 -0
- data/spec/flipper/exporters/json/export_spec.rb +60 -0
- data/spec/flipper/exporters/json/v1_spec.rb +33 -0
- data/spec/flipper/feature_check_context_spec.rb +5 -5
- data/spec/flipper/feature_spec.rb +76 -32
- data/spec/flipper/gates/boolean_spec.rb +1 -1
- data/spec/flipper/gates/group_spec.rb +2 -3
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -0
- data/spec/flipper/typecast_spec.rb +79 -0
- data/spec/flipper/types/actor_spec.rb +45 -45
- data/spec/flipper/types/group_spec.rb +2 -2
- data/spec/flipper_integration_spec.rb +62 -50
- data/spec/flipper_spec.rb +7 -1
- data/spec/support/skippable.rb +18 -0
- 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
|
33
|
+
it 'returns true' do
|
34
34
|
result = destination_flipper.import(source_flipper)
|
35
|
-
expect(result).to be(
|
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
|
-
|
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
|
data/spec/flipper/dsl_spec.rb
CHANGED
@@ -149,12 +149,12 @@ RSpec.describe Flipper::DSL do
|
|
149
149
|
end
|
150
150
|
|
151
151
|
describe '#actor' do
|
152
|
-
context 'for
|
152
|
+
context 'for an actor' do
|
153
153
|
it 'returns actor instance' do
|
154
|
-
|
155
|
-
|
156
|
-
expect(
|
157
|
-
expect(
|
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
|
-
|
347
|
-
expect(subject.
|
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(:
|
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
|
-
|
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.
|
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
|
35
|
-
options.delete(:
|
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
|
-
|
152
|
-
gate = subject.gate_for(
|
197
|
+
actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
|
198
|
+
gate = subject.gate_for(actor)
|
153
199
|
|
154
|
-
subject.enable(
|
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(
|
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
|
-
|
167
|
-
gate = subject.gate_for(
|
212
|
+
actor = Flipper::Actor.new('1')
|
213
|
+
gate = subject.gate_for(actor)
|
168
214
|
|
169
|
-
subject.enable(
|
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(
|
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
|
-
|
223
|
-
gate = subject.gate_for(
|
268
|
+
actor = Flipper::Actor.new('1')
|
269
|
+
gate = subject.gate_for(actor)
|
224
270
|
|
225
|
-
subject.disable(
|
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(
|
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
|
-
|
279
|
-
|
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[:
|
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[:
|
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) { |
|
432
|
-
@preview_features = Flipper.register(:preview_features) { |
|
433
|
-
@not_enabled = Flipper.register(:not_enabled) { |
|
434
|
-
@disabled = Flipper.register(:disabled) { |
|
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) { |
|
471
|
-
@preview_features = Flipper.register(:preview_features) { |
|
472
|
-
@not_enabled = Flipper.register(:not_enabled) { |
|
473
|
-
@disabled = Flipper.register(:disabled) { |
|
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) { |
|
498
|
-
@preview_features = Flipper.register(:preview_features) { |
|
499
|
-
@not_enabled = Flipper.register(:not_enabled) { |
|
500
|
-
@disabled = Flipper.register(:disabled) { |
|
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
|