flipper 0.22.1 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.github/workflows/ci.yml +26 -15
  5. data/.github/workflows/examples.yml +21 -15
  6. data/.rspec +1 -0
  7. data/.tool-versions +1 -0
  8. data/Changelog.md +144 -4
  9. data/Dockerfile +1 -1
  10. data/Gemfile +9 -6
  11. data/README.md +15 -67
  12. data/Rakefile +4 -2
  13. data/benchmark/enabled_ips.rb +10 -0
  14. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  15. data/benchmark/enabled_profile.rb +20 -0
  16. data/benchmark/instrumentation_ips.rb +21 -0
  17. data/benchmark/typecast_ips.rb +19 -0
  18. data/docs/README.md +1 -0
  19. data/docs/images/banner.jpg +0 -0
  20. data/examples/api/basic.ru +3 -4
  21. data/examples/api/custom_memoized.ru +3 -4
  22. data/examples/api/memoized.ru +3 -4
  23. data/examples/dsl.rb +3 -3
  24. data/examples/enabled_for_actor.rb +4 -2
  25. data/examples/instrumentation.rb +1 -0
  26. data/examples/instrumentation_last_accessed_at.rb +1 -0
  27. data/flipper.gemspec +2 -2
  28. data/lib/flipper/actor.rb +4 -0
  29. data/lib/flipper/adapter.rb +23 -7
  30. data/lib/flipper/adapters/dual_write.rb +10 -16
  31. data/lib/flipper/adapters/failover.rb +83 -0
  32. data/lib/flipper/adapters/failsafe.rb +76 -0
  33. data/lib/flipper/adapters/http/client.rb +18 -12
  34. data/lib/flipper/adapters/http/error.rb +19 -1
  35. data/lib/flipper/adapters/http.rb +14 -4
  36. data/lib/flipper/adapters/instrumented.rb +25 -2
  37. data/lib/flipper/adapters/memoizable.rb +27 -18
  38. data/lib/flipper/adapters/memory.rb +56 -39
  39. data/lib/flipper/adapters/operation_logger.rb +16 -3
  40. data/lib/flipper/adapters/poll/poller.rb +2 -0
  41. data/lib/flipper/adapters/poll.rb +39 -0
  42. data/lib/flipper/adapters/pstore.rb +2 -5
  43. data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
  44. data/lib/flipper/adapters/sync.rb +9 -15
  45. data/lib/flipper/dsl.rb +9 -11
  46. data/lib/flipper/errors.rb +3 -20
  47. data/lib/flipper/export.rb +26 -0
  48. data/lib/flipper/exporter.rb +17 -0
  49. data/lib/flipper/exporters/json/export.rb +32 -0
  50. data/lib/flipper/exporters/json/v1.rb +33 -0
  51. data/lib/flipper/feature.rb +32 -26
  52. data/lib/flipper/feature_check_context.rb +10 -6
  53. data/lib/flipper/gate.rb +12 -11
  54. data/lib/flipper/gate_values.rb +0 -16
  55. data/lib/flipper/gates/actor.rb +10 -17
  56. data/lib/flipper/gates/boolean.rb +1 -1
  57. data/lib/flipper/gates/group.rb +5 -7
  58. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  59. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  60. data/lib/flipper/identifier.rb +2 -2
  61. data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
  62. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  63. data/lib/flipper/instrumenters/memory.rb +6 -2
  64. data/lib/flipper/metadata.rb +1 -1
  65. data/lib/flipper/middleware/memoizer.rb +2 -12
  66. data/lib/flipper/poller.rb +117 -0
  67. data/lib/flipper/railtie.rb +23 -22
  68. data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
  69. data/lib/flipper/test/shared_adapter_test.rb +24 -0
  70. data/lib/flipper/typecast.rb +28 -15
  71. data/lib/flipper/types/actor.rb +19 -13
  72. data/lib/flipper/types/group.rb +12 -5
  73. data/lib/flipper/version.rb +1 -1
  74. data/lib/flipper.rb +6 -4
  75. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  76. data/spec/flipper/actor_spec.rb +10 -2
  77. data/spec/flipper/adapter_spec.rb +29 -4
  78. data/spec/flipper/adapters/dual_write_spec.rb +0 -2
  79. data/spec/flipper/adapters/failover_spec.rb +129 -0
  80. data/spec/flipper/adapters/failsafe_spec.rb +58 -0
  81. data/spec/flipper/adapters/http_spec.rb +64 -6
  82. data/spec/flipper/adapters/instrumented_spec.rb +28 -12
  83. data/spec/flipper/adapters/memoizable_spec.rb +30 -12
  84. data/spec/flipper/adapters/memory_spec.rb +3 -4
  85. data/spec/flipper/adapters/operation_logger_spec.rb +29 -12
  86. data/spec/flipper/adapters/pstore_spec.rb +0 -2
  87. data/spec/flipper/adapters/read_only_spec.rb +0 -1
  88. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
  89. data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
  90. data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
  91. data/spec/flipper/adapters/sync_spec.rb +0 -2
  92. data/spec/flipper/configuration_spec.rb +0 -1
  93. data/spec/flipper/dsl_spec.rb +38 -12
  94. data/spec/flipper/export_spec.rb +13 -0
  95. data/spec/flipper/exporter_spec.rb +16 -0
  96. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  97. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  98. data/spec/flipper/feature_check_context_spec.rb +17 -19
  99. data/spec/flipper/feature_spec.rb +76 -33
  100. data/spec/flipper/gate_spec.rb +0 -2
  101. data/spec/flipper/gate_values_spec.rb +2 -34
  102. data/spec/flipper/gates/actor_spec.rb +0 -2
  103. data/spec/flipper/gates/boolean_spec.rb +1 -3
  104. data/spec/flipper/gates/group_spec.rb +2 -5
  105. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
  106. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
  107. data/spec/flipper/identifier_spec.rb +0 -1
  108. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -6
  109. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -1
  110. data/spec/flipper/instrumenters/memory_spec.rb +18 -1
  111. data/spec/flipper/instrumenters/noop_spec.rb +14 -8
  112. data/spec/flipper/middleware/memoizer_spec.rb +0 -23
  113. data/spec/flipper/middleware/setup_env_spec.rb +0 -2
  114. data/spec/flipper/poller_spec.rb +47 -0
  115. data/spec/flipper/railtie_spec.rb +73 -33
  116. data/spec/flipper/registry_spec.rb +0 -1
  117. data/spec/flipper/typecast_spec.rb +82 -4
  118. data/spec/flipper/types/actor_spec.rb +45 -46
  119. data/spec/flipper/types/boolean_spec.rb +0 -1
  120. data/spec/flipper/types/group_spec.rb +2 -3
  121. data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
  122. data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
  123. data/spec/flipper/types/percentage_spec.rb +0 -1
  124. data/spec/flipper_integration_spec.rb +62 -51
  125. data/spec/flipper_spec.rb +7 -2
  126. data/spec/{helper.rb → spec_helper.rb} +4 -2
  127. data/spec/support/skippable.rb +18 -0
  128. data/spec/support/spec_helpers.rb +2 -6
  129. metadata +61 -19
  130. data/docs/Adapters.md +0 -124
  131. data/docs/Caveats.md +0 -4
  132. data/docs/Gates.md +0 -167
  133. data/docs/Instrumentation.md +0 -27
  134. data/docs/Optimization.md +0 -137
  135. data/docs/api/README.md +0 -884
  136. data/docs/http/README.md +0 -36
  137. 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
- expect(source).to eq({
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,6 +1,4 @@
1
- require 'helper'
2
1
  require 'flipper/adapters/pstore'
3
- require 'flipper/spec/shared_adapter_specs'
4
2
 
5
3
  RSpec.describe Flipper::Adapters::PStore do
6
4
  subject do
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/adapters/read_only'
3
2
 
4
3
  RSpec.describe Flipper::Adapters::ReadOnly do
@@ -1,4 +1,3 @@
1
- require "helper"
2
1
  require "flipper/adapters/memory"
3
2
  require "flipper/adapters/operation_logger"
4
3
  require "flipper/adapters/sync/feature_synchronizer"
@@ -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 << described_class.now } }
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(described_class).to receive(:now).and_return(now + i - 1)
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(described_class).to receive(:now).and_return(now + interval)
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
@@ -1,4 +1,3 @@
1
- require "helper"
2
1
  require "flipper/adapters/memory"
3
2
  require "flipper/instrumenters/memory"
4
3
  require "flipper/adapters/sync/synchronizer"
@@ -1,7 +1,5 @@
1
- require 'helper'
2
1
  require 'flipper/adapters/sync'
3
2
  require 'flipper/adapters/operation_logger'
4
- require 'flipper/spec/shared_adapter_specs'
5
3
  require 'active_support/notifications'
6
4
 
7
5
  RSpec.describe Flipper::Adapters::Sync do
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/configuration'
3
2
 
4
3
  RSpec.describe Flipper::Configuration do
@@ -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
- it 'sets adapter' do
11
- dsl = described_class.new(adapter)
12
- expect(dsl.adapter).not_to be_nil
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 a thing' do
152
+ context 'for an actor' do
144
153
  it 'returns actor instance' do
145
- thing = Flipper::Actor.new(33)
146
- actor = subject.actor(thing)
147
- expect(actor).to be_instance_of(Flipper::Types::Actor)
148
- 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')
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
- destination_flipper = build_flipper
338
- expect(subject.adapter).to receive(:import).with(destination_flipper.adapter)
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(:thing) { Flipper::Actor.new('5') }
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
- thing: thing,
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.thing).to eq(thing)
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(KeyError)
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(KeyError)
30
+ described_class.new(**options)
31
+ end.to raise_error(ArgumentError)
34
32
  end
35
33
 
36
- it 'requires thing' do
37
- options.delete(:thing)
34
+ it 'requires actors' do
35
+ options.delete(:actors)
38
36
  expect do
39
- described_class.new(options)
40
- end.to raise_error(KeyError)
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