flipper 0.22.0 → 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.
- 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
|