flipper 0.28.3 → 1.4.2

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +56 -10
  4. data/.github/workflows/examples.yml +54 -11
  5. data/.github/workflows/release.yml +54 -0
  6. data/.superset/config.json +4 -0
  7. data/CLAUDE.md +93 -0
  8. data/Changelog.md +1 -559
  9. data/Gemfile +17 -8
  10. data/README.md +34 -29
  11. data/Rakefile +2 -2
  12. data/benchmark/typecast_ips.rb +8 -0
  13. data/docs/images/banner.jpg +0 -0
  14. data/docs/images/flipper_cloud.png +0 -0
  15. data/examples/cloud/app.ru +12 -0
  16. data/examples/cloud/backoff_policy.rb +13 -0
  17. data/examples/cloud/basic.rb +22 -0
  18. data/examples/cloud/cloud_setup.rb +20 -0
  19. data/examples/cloud/forked.rb +36 -0
  20. data/examples/cloud/import.rb +17 -0
  21. data/examples/cloud/poll_interval/README.md +111 -0
  22. data/examples/cloud/poll_interval/client.rb +108 -0
  23. data/examples/cloud/poll_interval/server.rb +98 -0
  24. data/examples/cloud/threaded.rb +33 -0
  25. data/examples/dsl.rb +0 -14
  26. data/examples/expressions.rb +237 -0
  27. data/examples/strict.rb +18 -0
  28. data/exe/flipper +5 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +8 -4
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +27 -1
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/actor_limit.rb +54 -0
  35. data/lib/flipper/adapters/cache_base.rb +161 -0
  36. data/lib/flipper/adapters/dual_write.rb +7 -5
  37. data/lib/flipper/adapters/failover.rb +9 -7
  38. data/lib/flipper/adapters/failsafe.rb +2 -6
  39. data/lib/flipper/adapters/http/client.rb +53 -14
  40. data/lib/flipper/adapters/http/error.rb +2 -2
  41. data/lib/flipper/adapters/http.rb +56 -16
  42. data/lib/flipper/adapters/instrumented.rb +2 -6
  43. data/lib/flipper/adapters/memoizable.rb +17 -22
  44. data/lib/flipper/adapters/memory.rb +5 -7
  45. data/lib/flipper/adapters/operation_logger.rb +18 -92
  46. data/lib/flipper/adapters/poll.rb +16 -3
  47. data/lib/flipper/adapters/pstore.rb +18 -12
  48. data/lib/flipper/adapters/read_only.rb +8 -41
  49. data/lib/flipper/adapters/strict.rb +75 -0
  50. data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
  51. data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
  52. data/lib/flipper/adapters/sync.rb +7 -7
  53. data/lib/flipper/adapters/wrapper.rb +54 -0
  54. data/lib/flipper/cli.rb +314 -0
  55. data/lib/flipper/cloud/configuration.rb +267 -0
  56. data/lib/flipper/cloud/dsl.rb +27 -0
  57. data/lib/flipper/cloud/message_verifier.rb +95 -0
  58. data/lib/flipper/cloud/middleware.rb +63 -0
  59. data/lib/flipper/cloud/migrate.rb +71 -0
  60. data/lib/flipper/cloud/routes.rb +14 -0
  61. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  62. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  63. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  64. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  65. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  66. data/lib/flipper/cloud/telemetry.rb +191 -0
  67. data/lib/flipper/cloud.rb +54 -0
  68. data/lib/flipper/configuration.rb +25 -4
  69. data/lib/flipper/dsl.rb +48 -43
  70. data/lib/flipper/engine.rb +102 -0
  71. data/lib/flipper/export.rb +0 -2
  72. data/lib/flipper/exporters/json/export.rb +1 -1
  73. data/lib/flipper/exporters/json/v1.rb +1 -1
  74. data/lib/flipper/expression/builder.rb +73 -0
  75. data/lib/flipper/expression/constant.rb +25 -0
  76. data/lib/flipper/expression.rb +71 -0
  77. data/lib/flipper/expressions/all.rb +9 -0
  78. data/lib/flipper/expressions/any.rb +9 -0
  79. data/lib/flipper/expressions/boolean.rb +9 -0
  80. data/lib/flipper/expressions/comparable.rb +13 -0
  81. data/lib/flipper/expressions/equal.rb +9 -0
  82. data/lib/flipper/expressions/feature_enabled.rb +34 -0
  83. data/lib/flipper/expressions/greater_than.rb +9 -0
  84. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  85. data/lib/flipper/expressions/less_than.rb +9 -0
  86. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  87. data/lib/flipper/expressions/not_equal.rb +9 -0
  88. data/lib/flipper/expressions/now.rb +9 -0
  89. data/lib/flipper/expressions/number.rb +9 -0
  90. data/lib/flipper/expressions/percentage.rb +9 -0
  91. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  92. data/lib/flipper/expressions/property.rb +9 -0
  93. data/lib/flipper/expressions/random.rb +9 -0
  94. data/lib/flipper/expressions/string.rb +9 -0
  95. data/lib/flipper/expressions/time.rb +16 -0
  96. data/lib/flipper/feature.rb +63 -1
  97. data/lib/flipper/gate.rb +2 -1
  98. data/lib/flipper/gate_values.rb +5 -2
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/instrumentation/log_subscriber.rb +13 -5
  101. data/lib/flipper/instrumentation/statsd.rb +4 -2
  102. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  103. data/lib/flipper/instrumentation/subscriber.rb +0 -4
  104. data/lib/flipper/metadata.rb +8 -1
  105. data/lib/flipper/middleware/memoizer.rb +30 -14
  106. data/lib/flipper/model/active_record.rb +23 -0
  107. data/lib/flipper/poller.rb +54 -12
  108. data/lib/flipper/serializers/gzip.rb +22 -0
  109. data/lib/flipper/serializers/json.rb +17 -0
  110. data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
  111. data/lib/flipper/test/shared_adapter_test.rb +77 -58
  112. data/lib/flipper/test_help.rb +43 -0
  113. data/lib/flipper/typecast.rb +37 -9
  114. data/lib/flipper/types/percentage.rb +1 -1
  115. data/lib/flipper/version.rb +11 -1
  116. data/lib/flipper.rb +61 -8
  117. data/lib/generators/flipper/setup_generator.rb +68 -0
  118. data/lib/generators/flipper/templates/initializer.rb +45 -0
  119. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  120. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  121. data/lib/generators/flipper/update_generator.rb +35 -0
  122. data/package-lock.json +41 -0
  123. data/package.json +10 -0
  124. data/spec/fixtures/environment.rb +1 -0
  125. data/spec/flipper/adapter_builder_spec.rb +72 -0
  126. data/spec/flipper/adapter_spec.rb +21 -0
  127. data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
  128. data/spec/flipper/adapters/dual_write_spec.rb +15 -2
  129. data/spec/flipper/adapters/failover_spec.rb +12 -0
  130. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  131. data/spec/flipper/adapters/http_spec.rb +375 -74
  132. data/spec/flipper/adapters/instrumented_spec.rb +1 -1
  133. data/spec/flipper/adapters/memoizable_spec.rb +21 -21
  134. data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
  135. data/spec/flipper/adapters/poll_spec.rb +41 -0
  136. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  137. data/spec/flipper/adapters/strict_spec.rb +122 -0
  138. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -0
  139. data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
  140. data/spec/flipper/adapters/sync_spec.rb +13 -0
  141. data/spec/flipper/cli_spec.rb +217 -0
  142. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  143. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  144. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  145. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  146. data/spec/flipper/cloud/migrate_spec.rb +160 -0
  147. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  148. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  149. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  150. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  151. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  152. data/spec/flipper/cloud_spec.rb +186 -0
  153. data/spec/flipper/configuration_spec.rb +17 -0
  154. data/spec/flipper/dsl_spec.rb +34 -73
  155. data/spec/flipper/engine_spec.rb +374 -0
  156. data/spec/flipper/exporters/json/v1_spec.rb +3 -3
  157. data/spec/flipper/expression/builder_spec.rb +248 -0
  158. data/spec/flipper/expression_spec.rb +188 -0
  159. data/spec/flipper/expressions/all_spec.rb +15 -0
  160. data/spec/flipper/expressions/any_spec.rb +15 -0
  161. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  162. data/spec/flipper/expressions/equal_spec.rb +24 -0
  163. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  164. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  165. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  166. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  167. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  168. data/spec/flipper/expressions/now_spec.rb +11 -0
  169. data/spec/flipper/expressions/number_spec.rb +21 -0
  170. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  171. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  172. data/spec/flipper/expressions/property_spec.rb +13 -0
  173. data/spec/flipper/expressions/random_spec.rb +9 -0
  174. data/spec/flipper/expressions/string_spec.rb +11 -0
  175. data/spec/flipper/expressions/time_spec.rb +29 -0
  176. data/spec/flipper/feature_spec.rb +380 -10
  177. data/spec/flipper/gate_values_spec.rb +2 -2
  178. data/spec/flipper/gates/expression_spec.rb +190 -0
  179. data/spec/flipper/identifier_spec.rb +4 -5
  180. data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
  181. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
  182. data/spec/flipper/middleware/memoizer_spec.rb +107 -7
  183. data/spec/flipper/model/active_record_spec.rb +72 -0
  184. data/spec/flipper/poller_spec.rb +347 -4
  185. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  186. data/spec/flipper/serializers/json_spec.rb +13 -0
  187. data/spec/flipper/typecast_spec.rb +43 -7
  188. data/spec/flipper/types/actor_spec.rb +18 -1
  189. data/spec/flipper_integration_spec.rb +247 -16
  190. data/spec/flipper_spec.rb +92 -29
  191. data/spec/spec_helper.rb +24 -17
  192. data/spec/support/actor_names.yml +1 -0
  193. data/spec/support/fail_on_output.rb +8 -0
  194. data/spec/support/fake_backoff_policy.rb +15 -0
  195. data/spec/support/spec_helpers.rb +34 -8
  196. data/test/adapters/actor_limit_test.rb +20 -0
  197. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  198. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  199. data/test_rails/helper.rb +22 -2
  200. data/test_rails/system/test_help_test.rb +52 -0
  201. metadata +139 -75
  202. data/.tool-versions +0 -1
  203. data/lib/flipper/railtie.rb +0 -47
  204. data/spec/flipper/railtie_spec.rb +0 -109
@@ -0,0 +1,20 @@
1
+ RSpec.describe Flipper::Expressions::PercentageOfActors do
2
+ describe "#call" do
3
+ it "returns true when string in percentage enabled" do
4
+ expect(described_class.call("User;1", 42)).to be(true)
5
+ end
6
+
7
+ it "returns true when string in fractional percentage enabled" do
8
+ expect(described_class.call("User;1", 41.687)).to be(true)
9
+ end
10
+
11
+ it "returns false when string in percentage enabled" do
12
+ expect(described_class.call("User;1", 0)).to be(false)
13
+ end
14
+
15
+ it "changes value based on feature_name so not all actors get all features first" do
16
+ expect(described_class.call("User;1", 70, context: {feature_name: "a"})).to be(true)
17
+ expect(described_class.call("User;1", 70, context: {feature_name: "b"})).to be(false)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.describe Flipper::Expressions::Percentage do
2
+ describe "#call" do
3
+ it "returns numeric" do
4
+ expect(described_class.call(10)).to be(10.0)
5
+ end
6
+
7
+ it "returns 0 if less than 0" do
8
+ expect(described_class.call(-1)).to be(0)
9
+ end
10
+
11
+ it "returns 100 if greater than 100" do
12
+ expect(described_class.call(101)).to be(100)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ RSpec.describe Flipper::Expressions::Property do
2
+ describe "#call" do
3
+ it "returns value for property key" do
4
+ context = { properties: { "flipper_id" => "User;1" } }
5
+ expect(described_class.call("flipper_id", context: context)).to eq("User;1")
6
+ end
7
+
8
+ it "returns nil if key not found in properties" do
9
+ context = { properties: { } }
10
+ expect(described_class.call("flipper_id", context: context)).to be(nil)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ RSpec.describe Flipper::Expressions::Random do
2
+ describe "#call" do
3
+ it "returns random number based on max" do
4
+ 100.times do
5
+ expect(described_class.call(10)).to be_between(0, 10)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ RSpec.describe Flipper::Expressions::String do
2
+ describe "#call" do
3
+ it "returns String for Numeric" do
4
+ expect(described_class.call(10)).to eq("10")
5
+ end
6
+
7
+ it "returns String" do
8
+ expect(described_class.call("test")).to eq("test")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ RSpec.describe Flipper::Expressions::Time do
2
+ let(:time) { Time.now.round }
3
+
4
+ describe "#call" do
5
+ it "returns time for #to_s format" do
6
+ expect(described_class.call(time.to_s)).to eq(time)
7
+ end
8
+
9
+ it "returns time for #iso8601 format" do
10
+ expect(described_class.call(time.iso8601)).to eq(time)
11
+ end
12
+
13
+ it "returns time for epoch integer" do
14
+ expect(described_class.call(time.to_i)).to eq(Time.at(time.to_i).utc)
15
+ end
16
+
17
+ it "returns time for epoch float" do
18
+ expect(described_class.call(time.to_f)).to be_within(0.001).of(time)
19
+ end
20
+
21
+ it "returns utc for string input" do
22
+ expect(described_class.call(time.to_s).utc?).to be(true)
23
+ end
24
+
25
+ it "returns utc for numeric input" do
26
+ expect(described_class.call(time.to_i).utc?).to be(true)
27
+ end
28
+ end
29
+ end
@@ -76,6 +76,26 @@ RSpec.describe Flipper::Feature do
76
76
  expect(subject.enabled?(actors)).to be(false)
77
77
  end
78
78
  end
79
+
80
+ context "for an object that implements .nil? == true" do
81
+ let(:actor) { Flipper::Actor.new("User;1") }
82
+
83
+ before do
84
+ def actor.nil?
85
+ true
86
+ end
87
+ end
88
+
89
+ it 'returns true if feature is enabled' do
90
+ subject.enable
91
+ expect(subject.enabled?(actor)).to be(true)
92
+ end
93
+
94
+ it 'returns false if feature is disabled' do
95
+ subject.disable
96
+ expect(subject.enabled?(actor)).to be(false)
97
+ end
98
+ end
79
99
  end
80
100
 
81
101
  describe '#to_s' do
@@ -109,7 +129,7 @@ RSpec.describe Flipper::Feature do
109
129
  instance.gates.each do |gate|
110
130
  expect(gate).to be_a(Flipper::Gate)
111
131
  end
112
- expect(instance.gates.size).to be(5)
132
+ expect(instance.gates.size).to be(6)
113
133
  end
114
134
  end
115
135
 
@@ -195,7 +215,7 @@ RSpec.describe Flipper::Feature do
195
215
 
196
216
  it 'is recorded for enable' do
197
217
  actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
198
- gate = subject.gate_for(actor)
218
+ subject.gate_for(actor)
199
219
 
200
220
  subject.enable(actor)
201
221
 
@@ -210,7 +230,7 @@ RSpec.describe Flipper::Feature do
210
230
 
211
231
  it 'always instruments flipper type instance for enable' do
212
232
  actor = Flipper::Actor.new('1')
213
- gate = subject.gate_for(actor)
233
+ subject.gate_for(actor)
214
234
 
215
235
  subject.enable(actor)
216
236
 
@@ -221,7 +241,6 @@ RSpec.describe Flipper::Feature do
221
241
 
222
242
  it 'is recorded for disable' do
223
243
  thing = Flipper::Types::Boolean.new
224
- gate = subject.gate_for(thing)
225
244
 
226
245
  subject.disable(thing)
227
246
 
@@ -266,7 +285,6 @@ RSpec.describe Flipper::Feature do
266
285
 
267
286
  it 'always instruments flipper type instance for disable' do
268
287
  actor = Flipper::Actor.new('1')
269
- gate = subject.gate_for(actor)
270
288
 
271
289
  subject.disable(actor)
272
290
 
@@ -693,6 +711,361 @@ RSpec.describe Flipper::Feature do
693
711
  end
694
712
  end
695
713
 
714
+ describe '#expression' do
715
+ it "returns nil if feature has no expression" do
716
+ expect(subject.expression).to be(nil)
717
+ end
718
+
719
+ it "returns expression if feature has expression" do
720
+ expression = Flipper.property(:plan).eq("basic")
721
+ subject.enable_expression expression
722
+ expect(subject.expression).to eq(expression)
723
+ end
724
+ end
725
+
726
+ describe '#enable_expression/disable_expression' do
727
+ context "with expression instance" do
728
+ it "updates gate values to equal expression or clears expression" do
729
+ expression = Flipper.property(:plan).eq("basic")
730
+ expect(subject.gate_values.expression).to be(nil)
731
+ subject.enable_expression(expression)
732
+ expect(subject.gate_values.expression).to eq(expression.value)
733
+ subject.disable_expression
734
+ expect(subject.gate_values.expression).to be(nil)
735
+ end
736
+ end
737
+
738
+ context "with Hash" do
739
+ it "updates gate values to equal expression or clears expression" do
740
+ expression = Flipper.property(:plan).eq("basic")
741
+ expect(subject.gate_values.expression).to be(nil)
742
+ subject.enable_expression(expression.value)
743
+ expect(subject.gate_values.expression).to eq(expression.value)
744
+ subject.disable_expression
745
+ expect(subject.gate_values.expression).to be(nil)
746
+ end
747
+ end
748
+ end
749
+
750
+ describe "#add_expression" do
751
+ context "when nothing enabled" do
752
+ context "with Expression instance" do
753
+ it "sets expression to Expression" do
754
+ expression = Flipper.property(:plan).eq("basic")
755
+ subject.add_expression(expression)
756
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
757
+ expect(subject.expression).to eq(expression)
758
+ end
759
+ end
760
+
761
+ context "with Any instance" do
762
+ it "sets expression to Any" do
763
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
764
+ subject.add_expression(expression)
765
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
766
+ expect(subject.expression).to eq(expression)
767
+ end
768
+ end
769
+
770
+ context "with All instance" do
771
+ it "sets expression to All" do
772
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
773
+ subject.add_expression(expression)
774
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
775
+ expect(subject.expression).to eq(expression)
776
+ end
777
+ end
778
+ end
779
+
780
+ context "when Expression enabled" do
781
+ let(:expression) { Flipper.property(:plan).eq("basic") }
782
+
783
+ before do
784
+ subject.enable_expression expression
785
+ end
786
+
787
+ context "with Expression instance" do
788
+ it "changes expression to Any and adds new Expression" do
789
+ new_expression = Flipper.property(:age).gte(21)
790
+ subject.add_expression(new_expression)
791
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
792
+ expect(subject.expression.args).to include(expression)
793
+ expect(subject.expression.args).to include(new_expression)
794
+ end
795
+ end
796
+
797
+ context "with Any instance" do
798
+ it "changes expression to Any and adds new Any" do
799
+ new_expression = Flipper.any(Flipper.property(:age).eq(21))
800
+ subject.add_expression new_expression
801
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
802
+ expect(subject.expression.args).to include(expression)
803
+ expect(subject.expression.args).to include(new_expression)
804
+ end
805
+ end
806
+
807
+ context "with All instance" do
808
+ it "changes expression to Any and adds new All" do
809
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
810
+ subject.add_expression new_expression
811
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
812
+ expect(subject.expression.args).to include(expression)
813
+ expect(subject.expression.args).to include(new_expression)
814
+ end
815
+ end
816
+ end
817
+
818
+ context "when Any enabled" do
819
+ let(:condition) { Flipper.property(:plan).eq("basic") }
820
+ let(:expression) { Flipper.any(condition) }
821
+
822
+ before do
823
+ subject.enable_expression expression
824
+ end
825
+
826
+ context "with Expression instance" do
827
+ it "adds Expression to Any" do
828
+ new_expression = Flipper.property(:age).gte(21)
829
+ subject.add_expression(new_expression)
830
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
831
+ expect(subject.expression.args).to include(condition)
832
+ expect(subject.expression.args).to include(new_expression)
833
+ end
834
+ end
835
+
836
+ context "with Any instance" do
837
+ it "adds Any to Any" do
838
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
839
+ subject.add_expression(new_expression)
840
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
841
+ expect(subject.expression.args).to include(condition)
842
+ expect(subject.expression.args).to include(new_expression)
843
+ end
844
+ end
845
+
846
+ context "with All instance" do
847
+ it "adds All to Any" do
848
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
849
+ subject.add_expression(new_expression)
850
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
851
+ expect(subject.expression.args).to include(condition)
852
+ expect(subject.expression.args).to include(new_expression)
853
+ end
854
+ end
855
+ end
856
+
857
+ context "when All enabled" do
858
+ let(:condition) { Flipper.property(:plan).eq("basic") }
859
+ let(:expression) { Flipper.all(condition) }
860
+
861
+ before do
862
+ subject.enable_expression expression
863
+ end
864
+
865
+ context "with Expression instance" do
866
+ it "adds Expression to All" do
867
+ new_expression = Flipper.property(:age).gte(21)
868
+ subject.add_expression(new_expression)
869
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
870
+ expect(subject.expression.args).to include(condition)
871
+ expect(subject.expression.args).to include(new_expression)
872
+ end
873
+ end
874
+
875
+ context "with Any instance" do
876
+ it "adds Any to All" do
877
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
878
+ subject.add_expression(new_expression)
879
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
880
+ expect(subject.expression.args).to include(condition)
881
+ expect(subject.expression.args).to include(new_expression)
882
+ end
883
+ end
884
+
885
+ context "with All instance" do
886
+ it "adds All to All" do
887
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
888
+ subject.add_expression(new_expression)
889
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
890
+ expect(subject.expression.args).to include(condition)
891
+ expect(subject.expression.args).to include(new_expression)
892
+ end
893
+ end
894
+ end
895
+ end
896
+
897
+ describe '#remove_expression' do
898
+ context "when nothing enabled" do
899
+ context "with Expression instance" do
900
+ it "does nothing" do
901
+ expression = Flipper.property(:plan).eq("basic")
902
+ subject.remove_expression(expression)
903
+ expect(subject.expression).to be(nil)
904
+ end
905
+ end
906
+
907
+ context "with Any instance" do
908
+ it "does nothing" do
909
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
910
+ subject.remove_expression expression
911
+ expect(subject.expression).to be(nil)
912
+ end
913
+ end
914
+
915
+ context "with All instance" do
916
+ it "does nothing" do
917
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
918
+ subject.remove_expression expression
919
+ expect(subject.expression).to be(nil)
920
+ end
921
+ end
922
+ end
923
+
924
+ context "when Expression enabled" do
925
+ let(:expression) { Flipper.property(:plan).eq("basic") }
926
+
927
+ before do
928
+ subject.enable_expression expression
929
+ end
930
+
931
+ context "with Expression instance" do
932
+ it "changes expression to Any and removes Expression if it matches" do
933
+ new_expression = Flipper.property(:plan).eq("basic")
934
+ subject.remove_expression new_expression
935
+ expect(subject.expression).to eq(Flipper.any)
936
+ end
937
+
938
+ it "changes expression to Any if Expression doesn't match" do
939
+ new_expression = Flipper.property(:plan).eq("premium")
940
+ subject.remove_expression new_expression
941
+ expect(subject.expression).to eq(Flipper.any(expression))
942
+ end
943
+ end
944
+
945
+ context "with Any instance" do
946
+ it "changes expression to Any and does nothing" do
947
+ new_expression = Flipper.any(Flipper.property(:plan).eq("basic"))
948
+ subject.remove_expression new_expression
949
+ expect(subject.expression).to eq(Flipper.any(expression))
950
+ end
951
+ end
952
+
953
+ context "with All instance" do
954
+ it "changes expression to Any and does nothing" do
955
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
956
+ subject.remove_expression new_expression
957
+ expect(subject.expression).to eq(Flipper.any(expression))
958
+ end
959
+ end
960
+ end
961
+
962
+ context "when Any enabled" do
963
+ let(:condition) { Flipper.property(:plan).eq("basic") }
964
+ let(:expression) { Flipper.any condition }
965
+
966
+ before do
967
+ subject.enable_expression expression
968
+ end
969
+
970
+ context "with Expression instance" do
971
+ it "removes Expression if it matches" do
972
+ subject.remove_expression condition
973
+ expect(subject.expression).to eq(Flipper.any)
974
+ end
975
+
976
+ it "does nothing if Expression does not match" do
977
+ subject.remove_expression Flipper.property(:plan).eq("premium")
978
+ expect(subject.expression).to eq(expression)
979
+ end
980
+ end
981
+
982
+ context "with Any instance" do
983
+ it "removes Any if it matches" do
984
+ new_expression = Flipper.any(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 Any does not match" do
992
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
993
+ subject.remove_expression new_expression
994
+ expect(subject.expression).to eq(expression)
995
+ end
996
+ end
997
+
998
+ context "with All instance" do
999
+ it "removes All if it matches" do
1000
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1001
+ subject.add_expression new_expression
1002
+ expect(subject.expression.args.size).to be(2)
1003
+ subject.remove_expression new_expression
1004
+ expect(subject.expression).to eq(expression)
1005
+ end
1006
+
1007
+ it "does nothing if All does not match" do
1008
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1009
+ subject.remove_expression new_expression
1010
+ expect(subject.expression).to eq(expression)
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ context "when All enabled" do
1016
+ let(:condition) { Flipper.property(:plan).eq("basic") }
1017
+ let(:expression) { Flipper.all condition }
1018
+
1019
+ before do
1020
+ subject.enable_expression expression
1021
+ end
1022
+
1023
+ context "with Expression instance" do
1024
+ it "removes Expression if it matches" do
1025
+ subject.remove_expression condition
1026
+ expect(subject.expression).to eq(Flipper.all)
1027
+ end
1028
+
1029
+ it "does nothing if Expression does not match" do
1030
+ subject.remove_expression Flipper.property(:plan).eq("premium")
1031
+ expect(subject.expression).to eq(expression)
1032
+ end
1033
+ end
1034
+
1035
+ context "with Any instance" do
1036
+ it "removes Any if it matches" do
1037
+ new_expression = Flipper.any(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 Any does not match" do
1045
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
1046
+ subject.remove_expression new_expression
1047
+ expect(subject.expression).to eq(expression)
1048
+ end
1049
+ end
1050
+
1051
+ context "with All instance" do
1052
+ it "removes All if it matches" do
1053
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1054
+ subject.add_expression new_expression
1055
+ expect(subject.expression.args.size).to be(2)
1056
+ subject.remove_expression new_expression
1057
+ expect(subject.expression).to eq(expression)
1058
+ end
1059
+
1060
+ it "does nothing if All does not match" do
1061
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1062
+ subject.remove_expression new_expression
1063
+ expect(subject.expression).to eq(expression)
1064
+ end
1065
+ end
1066
+ end
1067
+ end
1068
+
696
1069
  describe '#enable_actor/disable_actor' do
697
1070
  context 'with object that responds to flipper_id' do
698
1071
  it 'updates the gate values to include the actor' do
@@ -721,8 +1094,6 @@ RSpec.describe Flipper::Feature do
721
1094
  describe '#enable_group/disable_group' do
722
1095
  context 'with symbol group name' do
723
1096
  it 'updates the gate values to include the group' do
724
- actor = Flipper::Actor.new(5)
725
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
726
1097
  expect(subject.gate_values.groups).to be_empty
727
1098
  subject.enable_group(:five_only)
728
1099
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -733,8 +1104,6 @@ RSpec.describe Flipper::Feature do
733
1104
 
734
1105
  context 'with string group name' do
735
1106
  it 'updates the gate values to include the group' do
736
- actor = Flipper::Actor.new(5)
737
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
738
1107
  expect(subject.gate_values.groups).to be_empty
739
1108
  subject.enable_group('five_only')
740
1109
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -745,7 +1114,6 @@ RSpec.describe Flipper::Feature do
745
1114
 
746
1115
  context 'with group instance' do
747
1116
  it 'updates the gate values for the group' do
748
- actor = Flipper::Actor.new(5)
749
1117
  group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
750
1118
  expect(subject.gate_values.groups).to be_empty
751
1119
  subject.enable_group(group)
@@ -845,12 +1213,14 @@ RSpec.describe Flipper::Feature do
845
1213
  :actor,
846
1214
  :boolean,
847
1215
  :group,
1216
+ :expression,
848
1217
  ])
849
1218
 
850
1219
  expect(subject.disabled_gate_names.to_set).to eq(Set[
851
1220
  :actor,
852
1221
  :boolean,
853
1222
  :group,
1223
+ :expression,
854
1224
  ])
855
1225
  end
856
1226
  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 a percentage))
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 a percentage))
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