flipper 0.26.0 → 1.1.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 (199) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +19 -13
  4. data/.github/workflows/examples.yml +32 -15
  5. data/Changelog.md +294 -154
  6. data/Gemfile +15 -10
  7. data/README.md +13 -11
  8. data/benchmark/enabled_ips.rb +10 -0
  9. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  10. data/benchmark/enabled_profile.rb +20 -0
  11. data/benchmark/instrumentation_ips.rb +21 -0
  12. data/benchmark/typecast_ips.rb +27 -0
  13. data/docs/images/flipper_cloud.png +0 -0
  14. data/examples/api/basic.ru +3 -4
  15. data/examples/api/custom_memoized.ru +3 -4
  16. data/examples/api/memoized.ru +3 -4
  17. data/examples/cloud/app.ru +12 -0
  18. data/examples/cloud/backoff_policy.rb +13 -0
  19. data/examples/cloud/basic.rb +22 -0
  20. data/examples/cloud/cloud_setup.rb +20 -0
  21. data/examples/cloud/forked.rb +36 -0
  22. data/examples/cloud/import.rb +17 -0
  23. data/examples/cloud/threaded.rb +33 -0
  24. data/examples/dsl.rb +1 -15
  25. data/examples/enabled_for_actor.rb +4 -2
  26. data/examples/expressions.rb +213 -0
  27. data/examples/mirroring.rb +59 -0
  28. data/examples/strict.rb +18 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +3 -5
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +33 -7
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/dual_write.rb +1 -3
  35. data/lib/flipper/adapters/failover.rb +0 -4
  36. data/lib/flipper/adapters/failsafe.rb +0 -4
  37. data/lib/flipper/adapters/http/client.rb +26 -7
  38. data/lib/flipper/adapters/http/error.rb +1 -1
  39. data/lib/flipper/adapters/http.rb +29 -16
  40. data/lib/flipper/adapters/instrumented.rb +25 -6
  41. data/lib/flipper/adapters/memoizable.rb +33 -21
  42. data/lib/flipper/adapters/memory.rb +81 -46
  43. data/lib/flipper/adapters/operation_logger.rb +16 -7
  44. data/lib/flipper/adapters/poll/poller.rb +2 -125
  45. data/lib/flipper/adapters/poll.rb +5 -3
  46. data/lib/flipper/adapters/pstore.rb +17 -11
  47. data/lib/flipper/adapters/read_only.rb +4 -4
  48. data/lib/flipper/adapters/strict.rb +47 -0
  49. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  50. data/lib/flipper/adapters/sync.rb +0 -4
  51. data/lib/flipper/cloud/configuration.rb +258 -0
  52. data/lib/flipper/cloud/dsl.rb +27 -0
  53. data/lib/flipper/cloud/message_verifier.rb +95 -0
  54. data/lib/flipper/cloud/middleware.rb +63 -0
  55. data/lib/flipper/cloud/routes.rb +14 -0
  56. data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
  57. data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -0
  58. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  59. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  60. data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
  61. data/lib/flipper/cloud/telemetry.rb +183 -0
  62. data/lib/flipper/cloud.rb +53 -0
  63. data/lib/flipper/configuration.rb +25 -4
  64. data/lib/flipper/dsl.rb +46 -45
  65. data/lib/flipper/engine.rb +88 -0
  66. data/lib/flipper/errors.rb +3 -3
  67. data/lib/flipper/export.rb +26 -0
  68. data/lib/flipper/exporter.rb +17 -0
  69. data/lib/flipper/exporters/json/export.rb +32 -0
  70. data/lib/flipper/exporters/json/v1.rb +33 -0
  71. data/lib/flipper/expression/builder.rb +73 -0
  72. data/lib/flipper/expression/constant.rb +25 -0
  73. data/lib/flipper/expression.rb +71 -0
  74. data/lib/flipper/expressions/all.rb +11 -0
  75. data/lib/flipper/expressions/any.rb +9 -0
  76. data/lib/flipper/expressions/boolean.rb +9 -0
  77. data/lib/flipper/expressions/comparable.rb +13 -0
  78. data/lib/flipper/expressions/duration.rb +28 -0
  79. data/lib/flipper/expressions/equal.rb +9 -0
  80. data/lib/flipper/expressions/greater_than.rb +9 -0
  81. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  82. data/lib/flipper/expressions/less_than.rb +9 -0
  83. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  84. data/lib/flipper/expressions/not_equal.rb +9 -0
  85. data/lib/flipper/expressions/now.rb +9 -0
  86. data/lib/flipper/expressions/number.rb +9 -0
  87. data/lib/flipper/expressions/percentage.rb +9 -0
  88. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  89. data/lib/flipper/expressions/property.rb +9 -0
  90. data/lib/flipper/expressions/random.rb +9 -0
  91. data/lib/flipper/expressions/string.rb +9 -0
  92. data/lib/flipper/expressions/time.rb +9 -0
  93. data/lib/flipper/feature.rb +87 -26
  94. data/lib/flipper/feature_check_context.rb +10 -6
  95. data/lib/flipper/gate.rb +13 -11
  96. data/lib/flipper/gate_values.rb +5 -18
  97. data/lib/flipper/gates/actor.rb +10 -17
  98. data/lib/flipper/gates/boolean.rb +1 -1
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/gates/group.rb +5 -7
  101. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  102. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  103. data/lib/flipper/identifier.rb +2 -2
  104. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  105. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  106. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  107. data/lib/flipper/metadata.rb +5 -1
  108. data/lib/flipper/middleware/memoizer.rb +30 -14
  109. data/lib/flipper/poller.rb +117 -0
  110. data/lib/flipper/serializers/gzip.rb +24 -0
  111. data/lib/flipper/serializers/json.rb +19 -0
  112. data/lib/flipper/spec/shared_adapter_specs.rb +95 -54
  113. data/lib/flipper/test/shared_adapter_test.rb +91 -48
  114. data/lib/flipper/typecast.rb +56 -15
  115. data/lib/flipper/types/actor.rb +13 -13
  116. data/lib/flipper/types/group.rb +4 -4
  117. data/lib/flipper/types/percentage.rb +1 -1
  118. data/lib/flipper/version.rb +1 -1
  119. data/lib/flipper.rb +47 -10
  120. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  121. data/spec/flipper/adapter_builder_spec.rb +73 -0
  122. data/spec/flipper/adapter_spec.rb +30 -2
  123. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  124. data/spec/flipper/adapters/http_spec.rb +64 -8
  125. data/spec/flipper/adapters/instrumented_spec.rb +29 -11
  126. data/spec/flipper/adapters/memoizable_spec.rb +51 -31
  127. data/spec/flipper/adapters/memory_spec.rb +14 -3
  128. data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
  129. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  130. data/spec/flipper/adapters/strict_spec.rb +62 -0
  131. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  132. data/spec/flipper/cloud/configuration_spec.rb +252 -0
  133. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  134. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  135. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  136. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +108 -0
  137. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  138. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  139. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  140. data/spec/flipper/cloud/telemetry_spec.rb +156 -0
  141. data/spec/flipper/cloud_spec.rb +180 -0
  142. data/spec/flipper/configuration_spec.rb +17 -0
  143. data/spec/flipper/dsl_spec.rb +54 -73
  144. data/spec/flipper/engine_spec.rb +291 -0
  145. data/spec/flipper/export_spec.rb +13 -0
  146. data/spec/flipper/exporter_spec.rb +16 -0
  147. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  148. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  149. data/spec/flipper/expression/builder_spec.rb +248 -0
  150. data/spec/flipper/expression_spec.rb +188 -0
  151. data/spec/flipper/expressions/all_spec.rb +15 -0
  152. data/spec/flipper/expressions/any_spec.rb +15 -0
  153. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  154. data/spec/flipper/expressions/duration_spec.rb +43 -0
  155. data/spec/flipper/expressions/equal_spec.rb +24 -0
  156. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  157. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  158. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  159. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  160. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  161. data/spec/flipper/expressions/now_spec.rb +11 -0
  162. data/spec/flipper/expressions/number_spec.rb +21 -0
  163. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  164. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  165. data/spec/flipper/expressions/property_spec.rb +13 -0
  166. data/spec/flipper/expressions/random_spec.rb +9 -0
  167. data/spec/flipper/expressions/string_spec.rb +11 -0
  168. data/spec/flipper/expressions/time_spec.rb +13 -0
  169. data/spec/flipper/feature_check_context_spec.rb +17 -17
  170. data/spec/flipper/feature_spec.rb +436 -33
  171. data/spec/flipper/gate_values_spec.rb +2 -33
  172. data/spec/flipper/gates/boolean_spec.rb +1 -1
  173. data/spec/flipper/gates/expression_spec.rb +108 -0
  174. data/spec/flipper/gates/group_spec.rb +2 -3
  175. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  176. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  177. data/spec/flipper/identifier_spec.rb +4 -5
  178. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
  179. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
  180. data/spec/flipper/middleware/memoizer_spec.rb +67 -0
  181. data/spec/flipper/poller_spec.rb +47 -0
  182. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  183. data/spec/flipper/serializers/json_spec.rb +13 -0
  184. data/spec/flipper/typecast_spec.rb +121 -6
  185. data/spec/flipper/types/actor_spec.rb +63 -46
  186. data/spec/flipper/types/group_spec.rb +2 -2
  187. data/spec/flipper_integration_spec.rb +168 -58
  188. data/spec/flipper_spec.rb +92 -28
  189. data/spec/spec_helper.rb +6 -13
  190. data/spec/support/actor_names.yml +1 -0
  191. data/spec/support/climate_control.rb +7 -0
  192. data/spec/support/fake_backoff_policy.rb +15 -0
  193. data/spec/support/skippable.rb +18 -0
  194. data/spec/support/spec_helpers.rb +11 -3
  195. metadata +166 -13
  196. data/.github/workflows/release.yml +0 -44
  197. data/.tool-versions +0 -1
  198. data/lib/flipper/railtie.rb +0 -47
  199. data/spec/flipper/railtie_spec.rb +0 -109
@@ -32,6 +32,52 @@ RSpec.describe Flipper::Feature do
32
32
  end
33
33
  end
34
34
 
35
+ describe "#enabled?" do
36
+ context "for an actor" do
37
+ let(:actor) { Flipper::Actor.new("User;1") }
38
+
39
+ it 'returns true if feature is enabled' do
40
+ subject.enable
41
+ expect(subject.enabled?(actor)).to be(true)
42
+ end
43
+
44
+ it 'returns false if feature is disabled' do
45
+ subject.disable
46
+ expect(subject.enabled?(actor)).to be(false)
47
+ end
48
+ end
49
+
50
+ context "for multiple actors" do
51
+ let(:actors) {
52
+ [
53
+ Flipper::Actor.new("User;1"),
54
+ Flipper::Actor.new("User;2"),
55
+ Flipper::Actor.new("User;3"),
56
+ ]
57
+ }
58
+
59
+ it 'returns true if feature is enabled' do
60
+ subject.enable
61
+ expect(subject.enabled?(actors)).to be(true)
62
+ end
63
+
64
+ it 'returns true if feature is enabled for any actor' do
65
+ subject.enable_actor actors.first
66
+ expect(subject.enabled?(actors)).to be(true)
67
+ end
68
+
69
+ it 'returns true if feature is enabled for any actor with multiple arguments' do
70
+ subject.enable_actor actors.last
71
+ expect(subject.enabled?(*actors)).to be(true)
72
+ end
73
+
74
+ it 'returns false if feature is disabled for all actors' do
75
+ subject.disable
76
+ expect(subject.enabled?(actors)).to be(false)
77
+ end
78
+ end
79
+ end
80
+
35
81
  describe '#to_s' do
36
82
  it 'returns name as string' do
37
83
  feature = described_class.new(:search, adapter)
@@ -63,7 +109,7 @@ RSpec.describe Flipper::Feature do
63
109
  instance.gates.each do |gate|
64
110
  expect(gate).to be_a(Flipper::Gate)
65
111
  end
66
- expect(instance.gates.size).to be(5)
112
+ expect(instance.gates.size).to be(6)
67
113
  end
68
114
  end
69
115
 
@@ -148,29 +194,29 @@ RSpec.describe Flipper::Feature do
148
194
  end
149
195
 
150
196
  it 'is recorded for enable' do
151
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
152
- gate = subject.gate_for(thing)
197
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
198
+ gate = subject.gate_for(actor)
153
199
 
154
- subject.enable(thing)
200
+ subject.enable(actor)
155
201
 
156
202
  event = instrumenter.events.last
157
203
  expect(event).not_to be_nil
158
204
  expect(event.name).to eq('feature_operation.flipper')
159
205
  expect(event.payload[:feature_name]).to eq(:search)
160
206
  expect(event.payload[:operation]).to eq(:enable)
161
- expect(event.payload[:thing]).to eq(thing)
207
+ expect(event.payload[:thing]).to eq(actor)
162
208
  expect(event.payload[:result]).not_to be_nil
163
209
  end
164
210
 
165
211
  it 'always instruments flipper type instance for enable' do
166
- thing = Flipper::Actor.new('1')
167
- gate = subject.gate_for(thing)
212
+ actor = Flipper::Actor.new('1')
213
+ gate = subject.gate_for(actor)
168
214
 
169
- subject.enable(thing)
215
+ subject.enable(actor)
170
216
 
171
217
  event = instrumenter.events.last
172
218
  expect(event).not_to be_nil
173
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
219
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
174
220
  end
175
221
 
176
222
  it 'is recorded for disable' do
@@ -219,15 +265,15 @@ RSpec.describe Flipper::Feature do
219
265
  end
220
266
 
221
267
  it 'always instruments flipper type instance for disable' do
222
- thing = Flipper::Actor.new('1')
223
- gate = subject.gate_for(thing)
268
+ actor = Flipper::Actor.new('1')
269
+ gate = subject.gate_for(actor)
224
270
 
225
- subject.disable(thing)
271
+ subject.disable(actor)
226
272
 
227
273
  event = instrumenter.events.last
228
274
  expect(event).not_to be_nil
229
275
  expect(event.payload[:operation]).to eq(:disable)
230
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
276
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
231
277
  end
232
278
 
233
279
  it 'is recorded for add' do
@@ -275,17 +321,15 @@ RSpec.describe Flipper::Feature do
275
321
  end
276
322
 
277
323
  it 'is recorded for enabled?' do
278
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
279
- gate = subject.gate_for(thing)
280
-
281
- subject.enabled?(thing)
324
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
325
+ subject.enabled?(actor)
282
326
 
283
327
  event = instrumenter.events.last
284
328
  expect(event).not_to be_nil
285
329
  expect(event.name).to eq('feature_operation.flipper')
286
330
  expect(event.payload[:feature_name]).to eq(:search)
287
331
  expect(event.payload[:operation]).to eq(:enabled?)
288
- expect(event.payload[:thing]).to eq(thing)
332
+ expect(event.payload[:actors]).to eq([actor])
289
333
  expect(event.payload[:result]).to eq(false)
290
334
  end
291
335
 
@@ -293,8 +337,8 @@ RSpec.describe Flipper::Feature do
293
337
  actor = Flipper::Types::Actor.new(user)
294
338
  {
295
339
  nil => nil,
296
- user => actor,
297
- actor => actor,
340
+ user => [actor],
341
+ actor => [actor],
298
342
  }.each do |thing, wrapped_thing|
299
343
  it "always instruments #{thing.inspect} as #{wrapped_thing.class} for enabled?" do
300
344
  subject.enabled?(thing)
@@ -302,7 +346,7 @@ RSpec.describe Flipper::Feature do
302
346
  event = instrumenter.events.last
303
347
  expect(event).not_to be_nil
304
348
  expect(event.payload[:operation]).to eq(:enabled?)
305
- expect(event.payload[:thing]).to eq(wrapped_thing)
349
+ expect(event.payload[:actors]).to eq(wrapped_thing)
306
350
  end
307
351
  end
308
352
  end
@@ -428,10 +472,10 @@ RSpec.describe Flipper::Feature do
428
472
 
429
473
  context 'when one or more groups enabled' do
430
474
  before do
431
- @staff = Flipper.register(:staff) { |_thing| true }
432
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
433
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
434
- @disabled = Flipper.register(:disabled) { |_thing| true }
475
+ @staff = Flipper.register(:staff) { |actor| true }
476
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
477
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
478
+ @disabled = Flipper.register(:disabled) { |actor| true }
435
479
  subject.enable @staff
436
480
  subject.enable @preview_features
437
481
  subject.disable @disabled
@@ -467,10 +511,10 @@ RSpec.describe Flipper::Feature do
467
511
 
468
512
  context 'when one or more groups enabled' do
469
513
  before do
470
- @staff = Flipper.register(:staff) { |_thing| true }
471
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
472
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
473
- @disabled = Flipper.register(:disabled) { |_thing| true }
514
+ @staff = Flipper.register(:staff) { |actor| true }
515
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
516
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
517
+ @disabled = Flipper.register(:disabled) { |actor| true }
474
518
  subject.enable @staff
475
519
  subject.enable @preview_features
476
520
  subject.disable @disabled
@@ -494,10 +538,10 @@ RSpec.describe Flipper::Feature do
494
538
 
495
539
  context 'when one or more groups enabled' do
496
540
  before do
497
- @staff = Flipper.register(:staff) { |_thing| true }
498
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
499
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
500
- @disabled = Flipper.register(:disabled) { |_thing| true }
541
+ @staff = Flipper.register(:staff) { |actor| true }
542
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
543
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
544
+ @disabled = Flipper.register(:disabled) { |actor| true }
501
545
  subject.enable @staff
502
546
  subject.enable @preview_features
503
547
  subject.disable @disabled
@@ -649,6 +693,363 @@ RSpec.describe Flipper::Feature do
649
693
  end
650
694
  end
651
695
 
696
+ describe '#expression' do
697
+ it "returns nil if feature has no expression" do
698
+ expect(subject.expression).to be(nil)
699
+ end
700
+
701
+ it "returns expression if feature has expression" do
702
+ expression = Flipper.property(:plan).eq("basic")
703
+ subject.enable_expression expression
704
+ expect(subject.expression).to eq(expression)
705
+ end
706
+ end
707
+
708
+ describe '#enable_expression/disable_expression' do
709
+ context "with expression instance" do
710
+ it "updates gate values to equal expression or clears expression" do
711
+ expression = Flipper.property(:plan).eq("basic")
712
+ other_expression = Flipper.property(:age).gte(21)
713
+ expect(subject.gate_values.expression).to be(nil)
714
+ subject.enable_expression(expression)
715
+ expect(subject.gate_values.expression).to eq(expression.value)
716
+ subject.disable_expression
717
+ expect(subject.gate_values.expression).to be(nil)
718
+ end
719
+ end
720
+
721
+ context "with Hash" do
722
+ it "updates gate values to equal expression or clears expression" do
723
+ expression = Flipper.property(:plan).eq("basic")
724
+ other_expression = Flipper.property(:age).gte(21)
725
+ expect(subject.gate_values.expression).to be(nil)
726
+ subject.enable_expression(expression.value)
727
+ expect(subject.gate_values.expression).to eq(expression.value)
728
+ subject.disable_expression
729
+ expect(subject.gate_values.expression).to be(nil)
730
+ end
731
+ end
732
+ end
733
+
734
+ describe "#add_expression" do
735
+ context "when nothing enabled" do
736
+ context "with Expression instance" do
737
+ it "sets expression to Expression" do
738
+ expression = Flipper.property(:plan).eq("basic")
739
+ subject.add_expression(expression)
740
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
741
+ expect(subject.expression).to eq(expression)
742
+ end
743
+ end
744
+
745
+ context "with Any instance" do
746
+ it "sets expression to Any" do
747
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
748
+ subject.add_expression(expression)
749
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
750
+ expect(subject.expression).to eq(expression)
751
+ end
752
+ end
753
+
754
+ context "with All instance" do
755
+ it "sets expression to All" do
756
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
757
+ subject.add_expression(expression)
758
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
759
+ expect(subject.expression).to eq(expression)
760
+ end
761
+ end
762
+ end
763
+
764
+ context "when Expression enabled" do
765
+ let(:expression) { Flipper.property(:plan).eq("basic") }
766
+
767
+ before do
768
+ subject.enable_expression expression
769
+ end
770
+
771
+ context "with Expression instance" do
772
+ it "changes expression to Any and adds new Expression" do
773
+ new_expression = Flipper.property(:age).gte(21)
774
+ subject.add_expression(new_expression)
775
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
776
+ expect(subject.expression.args).to include(expression)
777
+ expect(subject.expression.args).to include(new_expression)
778
+ end
779
+ end
780
+
781
+ context "with Any instance" do
782
+ it "changes expression to Any and adds new Any" do
783
+ new_expression = Flipper.any(Flipper.property(:age).eq(21))
784
+ subject.add_expression new_expression
785
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
786
+ expect(subject.expression.args).to include(expression)
787
+ expect(subject.expression.args).to include(new_expression)
788
+ end
789
+ end
790
+
791
+ context "with All instance" do
792
+ it "changes expression to Any and adds new All" do
793
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
794
+ subject.add_expression new_expression
795
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
796
+ expect(subject.expression.args).to include(expression)
797
+ expect(subject.expression.args).to include(new_expression)
798
+ end
799
+ end
800
+ end
801
+
802
+ context "when Any enabled" do
803
+ let(:condition) { Flipper.property(:plan).eq("basic") }
804
+ let(:expression) { Flipper.any(condition) }
805
+
806
+ before do
807
+ subject.enable_expression expression
808
+ end
809
+
810
+ context "with Expression instance" do
811
+ it "adds Expression to Any" do
812
+ new_expression = Flipper.property(:age).gte(21)
813
+ subject.add_expression(new_expression)
814
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
815
+ expect(subject.expression.args).to include(condition)
816
+ expect(subject.expression.args).to include(new_expression)
817
+ end
818
+ end
819
+
820
+ context "with Any instance" do
821
+ it "adds Any to Any" do
822
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
823
+ subject.add_expression(new_expression)
824
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
825
+ expect(subject.expression.args).to include(condition)
826
+ expect(subject.expression.args).to include(new_expression)
827
+ end
828
+ end
829
+
830
+ context "with All instance" do
831
+ it "adds All to Any" do
832
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
833
+ subject.add_expression(new_expression)
834
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
835
+ expect(subject.expression.args).to include(condition)
836
+ expect(subject.expression.args).to include(new_expression)
837
+ end
838
+ end
839
+ end
840
+
841
+ context "when All enabled" do
842
+ let(:condition) { Flipper.property(:plan).eq("basic") }
843
+ let(:expression) { Flipper.all(condition) }
844
+
845
+ before do
846
+ subject.enable_expression expression
847
+ end
848
+
849
+ context "with Expression instance" do
850
+ it "adds Expression to All" do
851
+ new_expression = Flipper.property(:age).gte(21)
852
+ subject.add_expression(new_expression)
853
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
854
+ expect(subject.expression.args).to include(condition)
855
+ expect(subject.expression.args).to include(new_expression)
856
+ end
857
+ end
858
+
859
+ context "with Any instance" do
860
+ it "adds Any to All" do
861
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
862
+ subject.add_expression(new_expression)
863
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
864
+ expect(subject.expression.args).to include(condition)
865
+ expect(subject.expression.args).to include(new_expression)
866
+ end
867
+ end
868
+
869
+ context "with All instance" do
870
+ it "adds All to All" do
871
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
872
+ subject.add_expression(new_expression)
873
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
874
+ expect(subject.expression.args).to include(condition)
875
+ expect(subject.expression.args).to include(new_expression)
876
+ end
877
+ end
878
+ end
879
+ end
880
+
881
+ describe '#remove_expression' do
882
+ context "when nothing enabled" do
883
+ context "with Expression instance" do
884
+ it "does nothing" do
885
+ expression = Flipper.property(:plan).eq("basic")
886
+ subject.remove_expression(expression)
887
+ expect(subject.expression).to be(nil)
888
+ end
889
+ end
890
+
891
+ context "with Any instance" do
892
+ it "does nothing" do
893
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
894
+ subject.remove_expression expression
895
+ expect(subject.expression).to be(nil)
896
+ end
897
+ end
898
+
899
+ context "with All instance" do
900
+ it "does nothing" do
901
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
902
+ subject.remove_expression expression
903
+ expect(subject.expression).to be(nil)
904
+ end
905
+ end
906
+ end
907
+
908
+ context "when Expression enabled" do
909
+ let(:expression) { Flipper.property(:plan).eq("basic") }
910
+
911
+ before do
912
+ subject.enable_expression expression
913
+ end
914
+
915
+ context "with Expression instance" do
916
+ it "changes expression to Any and removes Expression if it matches" do
917
+ new_expression = Flipper.property(:plan).eq("basic")
918
+ subject.remove_expression new_expression
919
+ expect(subject.expression).to eq(Flipper.any)
920
+ end
921
+
922
+ it "changes expression to Any if Expression doesn't match" do
923
+ new_expression = Flipper.property(:plan).eq("premium")
924
+ subject.remove_expression new_expression
925
+ expect(subject.expression).to eq(Flipper.any(expression))
926
+ end
927
+ end
928
+
929
+ context "with Any instance" do
930
+ it "changes expression to Any and does nothing" do
931
+ new_expression = Flipper.any(Flipper.property(:plan).eq("basic"))
932
+ subject.remove_expression new_expression
933
+ expect(subject.expression).to eq(Flipper.any(expression))
934
+ end
935
+ end
936
+
937
+ context "with All instance" do
938
+ it "changes expression to Any and does nothing" do
939
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
940
+ subject.remove_expression new_expression
941
+ expect(subject.expression).to eq(Flipper.any(expression))
942
+ end
943
+ end
944
+ end
945
+
946
+ context "when Any enabled" do
947
+ let(:condition) { Flipper.property(:plan).eq("basic") }
948
+ let(:expression) { Flipper.any condition }
949
+
950
+ before do
951
+ subject.enable_expression expression
952
+ end
953
+
954
+ context "with Expression instance" do
955
+ it "removes Expression if it matches" do
956
+ subject.remove_expression condition
957
+ expect(subject.expression).to eq(Flipper.any)
958
+ end
959
+
960
+ it "does nothing if Expression does not match" do
961
+ subject.remove_expression Flipper.property(:plan).eq("premium")
962
+ expect(subject.expression).to eq(expression)
963
+ end
964
+ end
965
+
966
+ context "with Any instance" do
967
+ it "removes Any if it matches" do
968
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
969
+ subject.add_expression new_expression
970
+ expect(subject.expression.args.size).to be(2)
971
+ subject.remove_expression new_expression
972
+ expect(subject.expression).to eq(expression)
973
+ end
974
+
975
+ it "does nothing if Any does not match" do
976
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
977
+ subject.remove_expression new_expression
978
+ expect(subject.expression).to eq(expression)
979
+ end
980
+ end
981
+
982
+ context "with All instance" do
983
+ it "removes All if it matches" do
984
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
985
+ subject.add_expression new_expression
986
+ expect(subject.expression.args.size).to be(2)
987
+ subject.remove_expression new_expression
988
+ expect(subject.expression).to eq(expression)
989
+ end
990
+
991
+ it "does nothing if All does not match" do
992
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
993
+ subject.remove_expression new_expression
994
+ expect(subject.expression).to eq(expression)
995
+ end
996
+ end
997
+ end
998
+
999
+ context "when All enabled" do
1000
+ let(:condition) { Flipper.property(:plan).eq("basic") }
1001
+ let(:expression) { Flipper.all condition }
1002
+
1003
+ before do
1004
+ subject.enable_expression expression
1005
+ end
1006
+
1007
+ context "with Expression instance" do
1008
+ it "removes Expression if it matches" do
1009
+ subject.remove_expression condition
1010
+ expect(subject.expression).to eq(Flipper.all)
1011
+ end
1012
+
1013
+ it "does nothing if Expression does not match" do
1014
+ subject.remove_expression Flipper.property(:plan).eq("premium")
1015
+ expect(subject.expression).to eq(expression)
1016
+ end
1017
+ end
1018
+
1019
+ context "with Any instance" do
1020
+ it "removes Any if it matches" do
1021
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
1022
+ subject.add_expression new_expression
1023
+ expect(subject.expression.args.size).to be(2)
1024
+ subject.remove_expression new_expression
1025
+ expect(subject.expression).to eq(expression)
1026
+ end
1027
+
1028
+ it "does nothing if Any does not match" do
1029
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
1030
+ subject.remove_expression new_expression
1031
+ expect(subject.expression).to eq(expression)
1032
+ end
1033
+ end
1034
+
1035
+ context "with All instance" do
1036
+ it "removes All if it matches" do
1037
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1038
+ subject.add_expression new_expression
1039
+ expect(subject.expression.args.size).to be(2)
1040
+ subject.remove_expression new_expression
1041
+ expect(subject.expression).to eq(expression)
1042
+ end
1043
+
1044
+ it "does nothing if All does not match" do
1045
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1046
+ subject.remove_expression new_expression
1047
+ expect(subject.expression).to eq(expression)
1048
+ end
1049
+ end
1050
+ end
1051
+ end
1052
+
652
1053
  describe '#enable_actor/disable_actor' do
653
1054
  context 'with object that responds to flipper_id' do
654
1055
  it 'updates the gate values to include the actor' do
@@ -801,12 +1202,14 @@ RSpec.describe Flipper::Feature do
801
1202
  :actor,
802
1203
  :boolean,
803
1204
  :group,
1205
+ :expression,
804
1206
  ])
805
1207
 
806
1208
  expect(subject.disabled_gate_names.to_set).to eq(Set[
807
1209
  :actor,
808
1210
  :boolean,
809
1211
  :group,
1212
+ :expression,
810
1213
  ])
811
1214
  end
812
1215
  end
@@ -80,13 +80,13 @@ RSpec.describe Flipper::GateValues do
80
80
  it 'raises argument error for percentage of time value that cannot be converted to an integer' do
81
81
  expect do
82
82
  described_class.new(percentage_of_time: ['asdf'])
83
- end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
83
+ end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a number))
84
84
  end
85
85
 
86
86
  it 'raises argument error for percentage of actors value that cannot be converted to an int' do
87
87
  expect do
88
88
  described_class.new(percentage_of_actors: ['asdf'])
89
- end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
89
+ end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a number))
90
90
  end
91
91
 
92
92
  it 'raises argument error for actors value that cannot be converted to a set' do
@@ -100,35 +100,4 @@ RSpec.describe Flipper::GateValues do
100
100
  described_class.new(groups: 'asdf')
101
101
  end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
102
102
  end
103
-
104
- describe '#[]' do
105
- it 'can read the boolean value' do
106
- expect(described_class.new(boolean: true)[:boolean]).to be(true)
107
- expect(described_class.new(boolean: true)['boolean']).to be(true)
108
- end
109
-
110
- it 'can read the actors value' do
111
- expect(described_class.new(actors: Set[1, 2])[:actors]).to eq(Set[1, 2])
112
- expect(described_class.new(actors: Set[1, 2])['actors']).to eq(Set[1, 2])
113
- end
114
-
115
- it 'can read the groups value' do
116
- expect(described_class.new(groups: Set[:admins])[:groups]).to eq(Set[:admins])
117
- expect(described_class.new(groups: Set[:admins])['groups']).to eq(Set[:admins])
118
- end
119
-
120
- it 'can read the percentage of time value' do
121
- expect(described_class.new(percentage_of_time: 15)[:percentage_of_time]).to eq(15)
122
- expect(described_class.new(percentage_of_time: 15)['percentage_of_time']).to eq(15)
123
- end
124
-
125
- it 'can read the percentage of actors value' do
126
- expect(described_class.new(percentage_of_actors: 15)[:percentage_of_actors]).to eq(15)
127
- expect(described_class.new(percentage_of_actors: 15)['percentage_of_actors']).to eq(15)
128
- end
129
-
130
- it 'returns nil for value that is not present' do
131
- expect(described_class.new({})['not legit']).to be(nil)
132
- end
133
- end
134
103
  end
@@ -9,7 +9,7 @@ RSpec.describe Flipper::Gates::Boolean do
9
9
  Flipper::FeatureCheckContext.new(
10
10
  feature_name: feature_name,
11
11
  values: Flipper::GateValues.new(boolean: bool),
12
- thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
12
+ actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
13
13
  )
14
14
  end
15
15