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.
Files changed (139) 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 -20
  5. data/.github/workflows/examples.yml +62 -0
  6. data/.rspec +1 -0
  7. data/.tool-versions +1 -0
  8. data/Changelog.md +152 -3
  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/docker-compose.yml +37 -34
  19. data/docs/README.md +1 -0
  20. data/docs/images/banner.jpg +0 -0
  21. data/examples/api/basic.ru +3 -4
  22. data/examples/api/custom_memoized.ru +3 -4
  23. data/examples/api/memoized.ru +3 -4
  24. data/examples/dsl.rb +3 -3
  25. data/examples/enabled_for_actor.rb +4 -2
  26. data/examples/instrumentation.rb +1 -0
  27. data/examples/instrumentation_last_accessed_at.rb +38 -0
  28. data/flipper.gemspec +2 -2
  29. data/lib/flipper/actor.rb +4 -0
  30. data/lib/flipper/adapter.rb +23 -7
  31. data/lib/flipper/adapters/dual_write.rb +10 -16
  32. data/lib/flipper/adapters/failover.rb +83 -0
  33. data/lib/flipper/adapters/failsafe.rb +76 -0
  34. data/lib/flipper/adapters/http/client.rb +18 -12
  35. data/lib/flipper/adapters/http/error.rb +19 -1
  36. data/lib/flipper/adapters/http.rb +14 -4
  37. data/lib/flipper/adapters/instrumented.rb +25 -2
  38. data/lib/flipper/adapters/memoizable.rb +27 -18
  39. data/lib/flipper/adapters/memory.rb +56 -39
  40. data/lib/flipper/adapters/operation_logger.rb +16 -3
  41. data/lib/flipper/adapters/poll/poller.rb +2 -0
  42. data/lib/flipper/adapters/poll.rb +39 -0
  43. data/lib/flipper/adapters/pstore.rb +2 -5
  44. data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
  45. data/lib/flipper/adapters/sync/synchronizer.rb +2 -1
  46. data/lib/flipper/adapters/sync.rb +9 -15
  47. data/lib/flipper/dsl.rb +9 -11
  48. data/lib/flipper/errors.rb +3 -20
  49. data/lib/flipper/export.rb +26 -0
  50. data/lib/flipper/exporter.rb +17 -0
  51. data/lib/flipper/exporters/json/export.rb +32 -0
  52. data/lib/flipper/exporters/json/v1.rb +33 -0
  53. data/lib/flipper/feature.rb +32 -26
  54. data/lib/flipper/feature_check_context.rb +10 -6
  55. data/lib/flipper/gate.rb +12 -11
  56. data/lib/flipper/gate_values.rb +0 -16
  57. data/lib/flipper/gates/actor.rb +10 -17
  58. data/lib/flipper/gates/boolean.rb +1 -1
  59. data/lib/flipper/gates/group.rb +5 -7
  60. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  61. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  62. data/lib/flipper/identifier.rb +2 -2
  63. data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
  64. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  65. data/lib/flipper/instrumenters/memory.rb +6 -2
  66. data/lib/flipper/metadata.rb +1 -1
  67. data/lib/flipper/middleware/memoizer.rb +2 -12
  68. data/lib/flipper/poller.rb +117 -0
  69. data/lib/flipper/railtie.rb +23 -22
  70. data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
  71. data/lib/flipper/test/shared_adapter_test.rb +24 -0
  72. data/lib/flipper/typecast.rb +28 -15
  73. data/lib/flipper/types/actor.rb +19 -13
  74. data/lib/flipper/types/group.rb +12 -5
  75. data/lib/flipper/version.rb +1 -1
  76. data/lib/flipper.rb +6 -4
  77. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  78. data/spec/flipper/actor_spec.rb +10 -2
  79. data/spec/flipper/adapter_spec.rb +29 -4
  80. data/spec/flipper/adapters/dual_write_spec.rb +0 -2
  81. data/spec/flipper/adapters/failover_spec.rb +129 -0
  82. data/spec/flipper/adapters/failsafe_spec.rb +58 -0
  83. data/spec/flipper/adapters/http_spec.rb +64 -6
  84. data/spec/flipper/adapters/instrumented_spec.rb +28 -12
  85. data/spec/flipper/adapters/memoizable_spec.rb +30 -12
  86. data/spec/flipper/adapters/memory_spec.rb +3 -4
  87. data/spec/flipper/adapters/operation_logger_spec.rb +29 -12
  88. data/spec/flipper/adapters/pstore_spec.rb +0 -2
  89. data/spec/flipper/adapters/read_only_spec.rb +0 -1
  90. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
  91. data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
  92. data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
  93. data/spec/flipper/adapters/sync_spec.rb +0 -2
  94. data/spec/flipper/configuration_spec.rb +0 -1
  95. data/spec/flipper/dsl_spec.rb +38 -12
  96. data/spec/flipper/export_spec.rb +13 -0
  97. data/spec/flipper/exporter_spec.rb +16 -0
  98. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  99. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  100. data/spec/flipper/feature_check_context_spec.rb +17 -19
  101. data/spec/flipper/feature_spec.rb +76 -33
  102. data/spec/flipper/gate_spec.rb +0 -2
  103. data/spec/flipper/gate_values_spec.rb +2 -34
  104. data/spec/flipper/gates/actor_spec.rb +0 -2
  105. data/spec/flipper/gates/boolean_spec.rb +1 -3
  106. data/spec/flipper/gates/group_spec.rb +2 -5
  107. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
  108. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
  109. data/spec/flipper/identifier_spec.rb +0 -1
  110. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -6
  111. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -1
  112. data/spec/flipper/instrumenters/memory_spec.rb +18 -1
  113. data/spec/flipper/instrumenters/noop_spec.rb +14 -8
  114. data/spec/flipper/middleware/memoizer_spec.rb +0 -23
  115. data/spec/flipper/middleware/setup_env_spec.rb +0 -2
  116. data/spec/flipper/poller_spec.rb +47 -0
  117. data/spec/flipper/railtie_spec.rb +73 -33
  118. data/spec/flipper/registry_spec.rb +0 -1
  119. data/spec/flipper/typecast_spec.rb +82 -4
  120. data/spec/flipper/types/actor_spec.rb +45 -46
  121. data/spec/flipper/types/boolean_spec.rb +0 -1
  122. data/spec/flipper/types/group_spec.rb +2 -3
  123. data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
  124. data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
  125. data/spec/flipper/types/percentage_spec.rb +0 -1
  126. data/spec/flipper_integration_spec.rb +62 -51
  127. data/spec/flipper_spec.rb +7 -2
  128. data/spec/{helper.rb → spec_helper.rb} +4 -2
  129. data/spec/support/skippable.rb +18 -0
  130. data/spec/support/spec_helpers.rb +2 -6
  131. metadata +63 -19
  132. data/docs/Adapters.md +0 -124
  133. data/docs/Caveats.md +0 -4
  134. data/docs/Gates.md +0 -167
  135. data/docs/Instrumentation.md +0 -27
  136. data/docs/Optimization.md +0 -137
  137. data/docs/api/README.md +0 -884
  138. data/docs/http/README.md +0 -36
  139. 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