flipper 0.22.0 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +26 -20
- data/.github/workflows/examples.yml +62 -0
- data/.rspec +1 -0
- data/.tool-versions +1 -0
- data/Changelog.md +152 -3
- data/Dockerfile +1 -1
- data/Gemfile +9 -6
- data/README.md +15 -67
- data/Rakefile +4 -2
- data/benchmark/enabled_ips.rb +10 -0
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/benchmark/enabled_profile.rb +20 -0
- data/benchmark/instrumentation_ips.rb +21 -0
- data/benchmark/typecast_ips.rb +19 -0
- data/docker-compose.yml +37 -34
- data/docs/README.md +1 -0
- data/docs/images/banner.jpg +0 -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/instrumentation.rb +1 -0
- data/examples/instrumentation_last_accessed_at.rb +38 -0
- data/flipper.gemspec +2 -2
- data/lib/flipper/actor.rb +4 -0
- data/lib/flipper/adapter.rb +23 -7
- data/lib/flipper/adapters/dual_write.rb +10 -16
- data/lib/flipper/adapters/failover.rb +83 -0
- data/lib/flipper/adapters/failsafe.rb +76 -0
- data/lib/flipper/adapters/http/client.rb +18 -12
- data/lib/flipper/adapters/http/error.rb +19 -1
- data/lib/flipper/adapters/http.rb +14 -4
- data/lib/flipper/adapters/instrumented.rb +25 -2
- data/lib/flipper/adapters/memoizable.rb +27 -18
- data/lib/flipper/adapters/memory.rb +56 -39
- data/lib/flipper/adapters/operation_logger.rb +16 -3
- data/lib/flipper/adapters/poll/poller.rb +2 -0
- data/lib/flipper/adapters/poll.rb +39 -0
- data/lib/flipper/adapters/pstore.rb +2 -5
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
- data/lib/flipper/adapters/sync/synchronizer.rb +2 -1
- data/lib/flipper/adapters/sync.rb +9 -15
- data/lib/flipper/dsl.rb +9 -11
- data/lib/flipper/errors.rb +3 -20
- 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 +32 -26
- data/lib/flipper/feature_check_context.rb +10 -6
- data/lib/flipper/gate.rb +12 -11
- data/lib/flipper/gate_values.rb +0 -16
- data/lib/flipper/gates/actor.rb +10 -17
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/group.rb +5 -7
- data/lib/flipper/gates/percentage_of_actors.rb +10 -13
- data/lib/flipper/gates/percentage_of_time.rb +1 -2
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/instrumenters/memory.rb +6 -2
- data/lib/flipper/metadata.rb +1 -1
- data/lib/flipper/middleware/memoizer.rb +2 -12
- data/lib/flipper/poller.rb +117 -0
- data/lib/flipper/railtie.rb +23 -22
- 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 +28 -15
- data/lib/flipper/types/actor.rb +19 -13
- data/lib/flipper/types/group.rb +12 -5
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +6 -4
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/actor_spec.rb +10 -2
- data/spec/flipper/adapter_spec.rb +29 -4
- data/spec/flipper/adapters/dual_write_spec.rb +0 -2
- data/spec/flipper/adapters/failover_spec.rb +129 -0
- data/spec/flipper/adapters/failsafe_spec.rb +58 -0
- data/spec/flipper/adapters/http_spec.rb +64 -6
- data/spec/flipper/adapters/instrumented_spec.rb +28 -12
- data/spec/flipper/adapters/memoizable_spec.rb +30 -12
- data/spec/flipper/adapters/memory_spec.rb +3 -4
- data/spec/flipper/adapters/operation_logger_spec.rb +29 -12
- data/spec/flipper/adapters/pstore_spec.rb +0 -2
- data/spec/flipper/adapters/read_only_spec.rb +0 -1
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync_spec.rb +0 -2
- data/spec/flipper/configuration_spec.rb +0 -1
- data/spec/flipper/dsl_spec.rb +38 -12
- 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 +17 -19
- data/spec/flipper/feature_spec.rb +76 -33
- data/spec/flipper/gate_spec.rb +0 -2
- data/spec/flipper/gate_values_spec.rb +2 -34
- data/spec/flipper/gates/actor_spec.rb +0 -2
- data/spec/flipper/gates/boolean_spec.rb +1 -3
- data/spec/flipper/gates/group_spec.rb +2 -5
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
- data/spec/flipper/identifier_spec.rb +0 -1
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -6
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -1
- data/spec/flipper/instrumenters/memory_spec.rb +18 -1
- data/spec/flipper/instrumenters/noop_spec.rb +14 -8
- data/spec/flipper/middleware/memoizer_spec.rb +0 -23
- data/spec/flipper/middleware/setup_env_spec.rb +0 -2
- data/spec/flipper/poller_spec.rb +47 -0
- data/spec/flipper/railtie_spec.rb +73 -33
- data/spec/flipper/registry_spec.rb +0 -1
- data/spec/flipper/typecast_spec.rb +82 -4
- data/spec/flipper/types/actor_spec.rb +45 -46
- data/spec/flipper/types/boolean_spec.rb +0 -1
- data/spec/flipper/types/group_spec.rb +2 -3
- data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
- data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
- data/spec/flipper/types/percentage_spec.rb +0 -1
- data/spec/flipper_integration_spec.rb +62 -51
- data/spec/flipper_spec.rb +7 -2
- data/spec/{helper.rb → spec_helper.rb} +4 -2
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +2 -6
- metadata +63 -19
- data/docs/Adapters.md +0 -124
- data/docs/Caveats.md +0 -4
- data/docs/Gates.md +0 -167
- data/docs/Instrumentation.md +0 -27
- data/docs/Optimization.md +0 -137
- data/docs/api/README.md +0 -884
- data/docs/http/README.md +0 -36
- data/docs/read-only/README.md +0 -24
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/adapters/instrumented'
|
3
2
|
require 'flipper/instrumenters/memory'
|
4
|
-
require 'flipper/spec/shared_adapter_specs'
|
5
3
|
|
6
4
|
RSpec.describe Flipper::Adapters::Instrumented do
|
7
5
|
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
@@ -18,16 +16,6 @@ RSpec.describe Flipper::Adapters::Instrumented do
|
|
18
16
|
|
19
17
|
it_should_behave_like 'a flipper adapter'
|
20
18
|
|
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
|
-
instrumented = described_class.new(adapter)
|
28
|
-
expect(instrumented.foo).to eq(:foo)
|
29
|
-
end
|
30
|
-
|
31
19
|
describe '#name' do
|
32
20
|
it 'is instrumented' do
|
33
21
|
expect(subject.name).to be(:instrumented)
|
@@ -148,4 +136,32 @@ RSpec.describe Flipper::Adapters::Instrumented do
|
|
148
136
|
expect(event.payload[:result]).to be(result)
|
149
137
|
end
|
150
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
|
151
167
|
end
|
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/adapters/memoizable'
|
3
2
|
require 'flipper/adapters/operation_logger'
|
4
|
-
require 'flipper/spec/shared_adapter_specs'
|
5
3
|
|
6
4
|
RSpec.describe Flipper::Adapters::Memoizable do
|
7
5
|
let(:features_key) { described_class::FeaturesKey }
|
@@ -13,16 +11,6 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
13
11
|
|
14
12
|
it_should_behave_like 'a flipper adapter'
|
15
13
|
|
16
|
-
it 'forwards missing methods to underlying adapter' do
|
17
|
-
adapter = Class.new do
|
18
|
-
def foo
|
19
|
-
:foo
|
20
|
-
end
|
21
|
-
end.new
|
22
|
-
memoizable = described_class.new(adapter)
|
23
|
-
expect(memoizable.foo).to eq(:foo)
|
24
|
-
end
|
25
|
-
|
26
14
|
describe '#name' do
|
27
15
|
it 'is instrumented' do
|
28
16
|
expect(subject.name).to be(:memoizable)
|
@@ -251,6 +239,36 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
251
239
|
end
|
252
240
|
end
|
253
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
|
+
|
254
272
|
describe '#features' do
|
255
273
|
context 'with memoization enabled' do
|
256
274
|
before do
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'flipper/spec/shared_adapter_specs'
|
3
|
-
|
4
1
|
RSpec.describe Flipper::Adapters::Memory do
|
5
2
|
let(:source) { {} }
|
6
3
|
subject { described_class.new(source) }
|
@@ -17,7 +14,9 @@ RSpec.describe Flipper::Adapters::Memory do
|
|
17
14
|
flipper.enable_actor :following, Flipper::Actor.new('3')
|
18
15
|
flipper.enable_group :following, Flipper::Types::Group.new(:staff)
|
19
16
|
|
20
|
-
|
17
|
+
dup = described_class.new(subject.get_all)
|
18
|
+
|
19
|
+
expect(dup.get_all).to eq({
|
21
20
|
"subscriptions" => subject.default_config.merge(boolean: "true"),
|
22
21
|
"search" => subject.default_config,
|
23
22
|
"logging" => subject.default_config.merge(:percentage_of_time => "30"),
|
@@ -1,6 +1,4 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/adapters/operation_logger'
|
3
|
-
require 'flipper/spec/shared_adapter_specs'
|
4
2
|
|
5
3
|
RSpec.describe Flipper::Adapters::OperationLogger do
|
6
4
|
let(:operations) { [] }
|
@@ -20,16 +18,6 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
20
18
|
expect(output).to match(/@adapter=#<Flipper::Adapters::Memory/)
|
21
19
|
end
|
22
20
|
|
23
|
-
it 'forwards missing methods to underlying adapter' do
|
24
|
-
adapter = Class.new do
|
25
|
-
def foo
|
26
|
-
:foo
|
27
|
-
end
|
28
|
-
end.new
|
29
|
-
operation_logger = described_class.new(adapter)
|
30
|
-
expect(operation_logger.foo).to eq(:foo)
|
31
|
-
end
|
32
|
-
|
33
21
|
describe '#get' do
|
34
22
|
before do
|
35
23
|
@feature = flipper[:stats]
|
@@ -108,4 +96,33 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
108
96
|
expect(@result).to eq(adapter.add(@feature))
|
109
97
|
end
|
110
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
|
111
128
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require "helper"
|
2
1
|
require "flipper/adapters/sync/interval_synchronizer"
|
3
2
|
|
4
3
|
RSpec.describe Flipper::Adapters::Sync::IntervalSynchronizer do
|
5
4
|
let(:events) { [] }
|
6
|
-
let(:synchronizer) { -> { events <<
|
5
|
+
let(:synchronizer) { -> { events << now } }
|
7
6
|
let(:interval) { 10 }
|
7
|
+
let(:now) { subject.send(:now) }
|
8
8
|
|
9
9
|
subject { described_class.new(synchronizer, interval: interval) }
|
10
10
|
|
@@ -15,19 +15,18 @@ RSpec.describe Flipper::Adapters::Sync::IntervalSynchronizer do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it "only invokes wrapped synchronizer every interval seconds" do
|
18
|
-
now = described_class.now
|
19
18
|
subject.call
|
20
19
|
events.clear
|
21
20
|
|
22
21
|
# move time to one millisecond less than last sync + interval
|
23
22
|
1.upto(interval) do |i|
|
24
|
-
allow(
|
23
|
+
allow(subject).to receive(:now).and_return(now + i - 1)
|
25
24
|
subject.call
|
26
25
|
end
|
27
26
|
expect(events.size).to be(0)
|
28
27
|
|
29
28
|
# move time to last sync + interval in milliseconds
|
30
|
-
allow(
|
29
|
+
allow(subject).to receive(:now).and_return(now + interval)
|
31
30
|
subject.call
|
32
31
|
expect(events.size).to be(1)
|
33
32
|
end
|
data/spec/flipper/dsl_spec.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/dsl'
|
3
2
|
|
4
3
|
RSpec.describe Flipper::DSL do
|
@@ -7,9 +6,19 @@ RSpec.describe Flipper::DSL do
|
|
7
6
|
let(:adapter) { Flipper::Adapters::Memory.new }
|
8
7
|
|
9
8
|
describe '#initialize' do
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
context 'when using default memoize strategy' do
|
10
|
+
it 'wraps the given adapter with Flipper::Adapters::Memoizable' do
|
11
|
+
dsl = described_class.new(adapter)
|
12
|
+
expect(dsl.adapter.class).to be(Flipper::Adapters::Memoizable)
|
13
|
+
expect(dsl.adapter.adapter).to be(adapter)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when disabling memoization' do
|
18
|
+
it 'uses the given adapter directly' do
|
19
|
+
dsl = described_class.new(adapter, memoize: false)
|
20
|
+
expect(dsl.adapter).to be(adapter)
|
21
|
+
end
|
13
22
|
end
|
14
23
|
|
15
24
|
it 'defaults instrumenter to noop' do
|
@@ -140,12 +149,12 @@ RSpec.describe Flipper::DSL do
|
|
140
149
|
end
|
141
150
|
|
142
151
|
describe '#actor' do
|
143
|
-
context 'for
|
152
|
+
context 'for an actor' do
|
144
153
|
it 'returns actor instance' do
|
145
|
-
|
146
|
-
|
147
|
-
expect(
|
148
|
-
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')
|
149
158
|
end
|
150
159
|
end
|
151
160
|
|
@@ -333,10 +342,27 @@ RSpec.describe Flipper::DSL do
|
|
333
342
|
end
|
334
343
|
|
335
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
|
336
363
|
it 'delegates to adapter' do
|
337
|
-
|
338
|
-
expect(subject.
|
339
|
-
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))
|
340
366
|
end
|
341
367
|
end
|
342
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,67 +1,65 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
1
|
RSpec.describe Flipper::FeatureCheckContext do
|
4
2
|
let(:feature_name) { :new_profiles }
|
5
3
|
let(:values) { Flipper::GateValues.new({}) }
|
6
|
-
let(:
|
4
|
+
let(:actor) { Flipper::Actor.new('5') }
|
7
5
|
let(:options) do
|
8
6
|
{
|
9
7
|
feature_name: feature_name,
|
10
8
|
values: values,
|
11
|
-
|
9
|
+
actors: [actor],
|
12
10
|
}
|
13
11
|
end
|
14
12
|
|
15
13
|
it 'initializes just fine' do
|
16
|
-
instance = described_class.new(options)
|
14
|
+
instance = described_class.new(**options)
|
17
15
|
expect(instance.feature_name).to eq(feature_name)
|
18
16
|
expect(instance.values).to eq(values)
|
19
|
-
expect(instance.
|
17
|
+
expect(instance.actors).to eq([actor])
|
20
18
|
end
|
21
19
|
|
22
20
|
it 'requires feature_name' do
|
23
21
|
options.delete(:feature_name)
|
24
22
|
expect do
|
25
|
-
described_class.new(options)
|
26
|
-
end.to raise_error(
|
23
|
+
described_class.new(**options)
|
24
|
+
end.to raise_error(ArgumentError)
|
27
25
|
end
|
28
26
|
|
29
27
|
it 'requires values' do
|
30
28
|
options.delete(:values)
|
31
29
|
expect do
|
32
|
-
described_class.new(options)
|
33
|
-
end.to raise_error(
|
30
|
+
described_class.new(**options)
|
31
|
+
end.to raise_error(ArgumentError)
|
34
32
|
end
|
35
33
|
|
36
|
-
it 'requires
|
37
|
-
options.delete(:
|
34
|
+
it 'requires actors' do
|
35
|
+
options.delete(:actors)
|
38
36
|
expect do
|
39
|
-
described_class.new(options)
|
40
|
-
end.to raise_error(
|
37
|
+
described_class.new(**options)
|
38
|
+
end.to raise_error(ArgumentError)
|
41
39
|
end
|
42
40
|
|
43
41
|
it 'knows actors_value' do
|
44
42
|
args = options.merge(values: Flipper::GateValues.new(actors: Set['User;1']))
|
45
|
-
expect(described_class.new(args).actors_value).to eq(Set['User;1'])
|
43
|
+
expect(described_class.new(**args).actors_value).to eq(Set['User;1'])
|
46
44
|
end
|
47
45
|
|
48
46
|
it 'knows groups_value' do
|
49
47
|
args = options.merge(values: Flipper::GateValues.new(groups: Set['admins']))
|
50
|
-
expect(described_class.new(args).groups_value).to eq(Set['admins'])
|
48
|
+
expect(described_class.new(**args).groups_value).to eq(Set['admins'])
|
51
49
|
end
|
52
50
|
|
53
51
|
it 'knows boolean_value' do
|
54
|
-
instance = described_class.new(options.merge(values: Flipper::GateValues.new(boolean: true)))
|
52
|
+
instance = described_class.new(**options.merge(values: Flipper::GateValues.new(boolean: true)))
|
55
53
|
expect(instance.boolean_value).to eq(true)
|
56
54
|
end
|
57
55
|
|
58
56
|
it 'knows percentage_of_actors_value' do
|
59
57
|
args = options.merge(values: Flipper::GateValues.new(percentage_of_actors: 14))
|
60
|
-
expect(described_class.new(args).percentage_of_actors_value).to eq(14)
|
58
|
+
expect(described_class.new(**args).percentage_of_actors_value).to eq(14)
|
61
59
|
end
|
62
60
|
|
63
61
|
it 'knows percentage_of_time_value' do
|
64
62
|
args = options.merge(values: Flipper::GateValues.new(percentage_of_time: 41))
|
65
|
-
expect(described_class.new(args).percentage_of_time_value).to eq(41)
|
63
|
+
expect(described_class.new(**args).percentage_of_time_value).to eq(41)
|
66
64
|
end
|
67
65
|
end
|