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
@@ -0,0 +1,188 @@
1
+ require 'flipper/expression'
2
+
3
+ RSpec.describe Flipper::Expression do
4
+ describe "#build" do
5
+ it "can build Equal" do
6
+ expression = described_class.build({
7
+ "Equal" => [
8
+ "basic",
9
+ "basic",
10
+ ]
11
+ })
12
+
13
+ expect(expression).to be_instance_of(Flipper::Expression)
14
+ expect(expression.function).to be(Flipper::Expressions::Equal)
15
+ expect(expression.args).to eq([
16
+ Flipper.constant("basic"),
17
+ Flipper.constant("basic"),
18
+ ])
19
+ end
20
+
21
+ it "can build GreaterThanOrEqualTo" do
22
+ expression = described_class.build({
23
+ "GreaterThanOrEqualTo" => [
24
+ 2,
25
+ 1,
26
+ ]
27
+ })
28
+
29
+ expect(expression).to be_instance_of(Flipper::Expression)
30
+ expect(expression.function).to be(Flipper::Expressions::GreaterThanOrEqualTo)
31
+ expect(expression.args).to eq([
32
+ Flipper.constant(2),
33
+ Flipper.constant(1),
34
+ ])
35
+ end
36
+
37
+ it "can build GreaterThan" do
38
+ expression = described_class.build({
39
+ "GreaterThan" => [
40
+ 2,
41
+ 1,
42
+ ]
43
+ })
44
+
45
+ expect(expression).to be_instance_of(Flipper::Expression)
46
+ expect(expression.function).to be(Flipper::Expressions::GreaterThan)
47
+ expect(expression.args).to eq([
48
+ Flipper.constant(2),
49
+ Flipper.constant(1),
50
+ ])
51
+ end
52
+
53
+ it "can build LessThanOrEqualTo" do
54
+ expression = described_class.build({
55
+ "LessThanOrEqualTo" => [
56
+ 2,
57
+ 1,
58
+ ]
59
+ })
60
+
61
+ expect(expression).to be_instance_of(Flipper::Expression)
62
+ expect(expression.function).to be(Flipper::Expressions::LessThanOrEqualTo)
63
+ expect(expression.args).to eq([
64
+ Flipper.constant(2),
65
+ Flipper.constant(1),
66
+ ])
67
+ end
68
+
69
+ it "can build LessThan" do
70
+ expression = described_class.build({
71
+ "LessThan" => [2, 1]
72
+ })
73
+
74
+ expect(expression).to be_instance_of(Flipper::Expression)
75
+ expect(expression.function).to be(Flipper::Expressions::LessThan)
76
+ expect(expression.args).to eq([
77
+ Flipper.constant(2),
78
+ Flipper.constant(1),
79
+ ])
80
+ end
81
+
82
+ it "can build NotEqual" do
83
+ expression = described_class.build({
84
+ "NotEqual" => [
85
+ "basic",
86
+ "plus",
87
+ ]
88
+ })
89
+
90
+ expect(expression).to be_instance_of(Flipper::Expression)
91
+ expect(expression.function).to be(Flipper::Expressions::NotEqual)
92
+ expect(expression.args).to eq([
93
+ Flipper.constant("basic"),
94
+ Flipper.constant("plus"),
95
+ ])
96
+ end
97
+
98
+ it "can build Number" do
99
+ expression = described_class.build(1)
100
+
101
+ expect(expression).to be_instance_of(Flipper::Expression::Constant)
102
+ expect(expression.value).to eq(1)
103
+ end
104
+
105
+ it "can build Percentage" do
106
+ expression = described_class.build({
107
+ "Percentage" => [1]
108
+ })
109
+
110
+ expect(expression).to be_instance_of(Flipper::Expression)
111
+ expect(expression.function).to be(Flipper::Expressions::Percentage)
112
+ expect(expression.args).to eq([Flipper.constant(1)])
113
+ end
114
+
115
+ it "can build PercentageOfActors" do
116
+ expression = described_class.build({
117
+ "PercentageOfActors" => [
118
+ "User;1",
119
+ 40,
120
+ ]
121
+ })
122
+
123
+ expect(expression).to be_instance_of(Flipper::Expression)
124
+ expect(expression.function).to be(Flipper::Expressions::PercentageOfActors)
125
+ expect(expression.args).to eq([
126
+ Flipper.constant("User;1"),
127
+ Flipper.constant(40),
128
+ ])
129
+ end
130
+
131
+ it "can build String" do
132
+ expression = described_class.build("basic")
133
+
134
+ expect(expression).to be_instance_of(Flipper::Expression::Constant)
135
+ expect(expression.value).to eq("basic")
136
+ end
137
+
138
+ it "can build Property" do
139
+ expression = described_class.build({
140
+ "Property" => ["flipper_id"]
141
+ })
142
+
143
+ expect(expression).to be_instance_of(Flipper::Expression)
144
+ expect(expression.function).to be(Flipper::Expressions::Property)
145
+ expect(expression.args).to eq([Flipper.constant("flipper_id")])
146
+ end
147
+ end
148
+
149
+ describe "#eql?" do
150
+ it "returns true for same class and args" do
151
+ expression = described_class.build("foo")
152
+ other = described_class.build("foo")
153
+ expect(expression.eql?(other)).to be(true)
154
+ end
155
+
156
+ it "returns false for different class" do
157
+ expression = described_class.build("foo")
158
+ other = Object.new
159
+ expect(expression.eql?(other)).to be(false)
160
+ end
161
+
162
+ it "returns false for different args" do
163
+ expression = described_class.build("foo")
164
+ other = described_class.build("bar")
165
+ expect(expression.eql?(other)).to be(false)
166
+ end
167
+ end
168
+
169
+ describe "#==" do
170
+ it "returns true for same class and args" do
171
+ expression = described_class.build("foo")
172
+ other = described_class.build("foo")
173
+ expect(expression == other).to be(true)
174
+ end
175
+
176
+ it "returns false for different class" do
177
+ expression = described_class.build("foo")
178
+ other = Object.new
179
+ expect(expression == other).to be(false)
180
+ end
181
+
182
+ it "returns false for different args" do
183
+ expression = described_class.build("foo")
184
+ other = described_class.build("bar")
185
+ expect(expression == other).to be(false)
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.describe Flipper::Expressions::All do
2
+ describe "#call" do
3
+ it "returns true if all args evaluate as true" do
4
+ expect(described_class.call(true, true)).to be(true)
5
+ end
6
+
7
+ it "returns false if any args evaluate as false" do
8
+ expect(described_class.call(false, true)).to be(false)
9
+ end
10
+
11
+ it "returns true with empty args" do
12
+ expect(described_class.call).to be(true)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.describe Flipper::Expressions::Any do
2
+ describe "#call" do
3
+ it "returns true if any args evaluate as true" do
4
+ expect(described_class.call(true, false)).to be(true)
5
+ end
6
+
7
+ it "returns false if all args evaluate as false" do
8
+ expect(described_class.call(false, false)).to be(false)
9
+ end
10
+
11
+ it "returns false with empty args" do
12
+ expect(described_class.call).to be(false)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.describe Flipper::Expressions::Boolean do
2
+ describe "#call" do
3
+ [true, 'true', 1, '1'].each do |value|
4
+ it "returns a true for #{value.inspect}" do
5
+ expect(described_class.call(value)).to be(true)
6
+ end
7
+ end
8
+
9
+ [false, 'false', 0, '0', nil].each do |value|
10
+ it "returns a true for #{value.inspect}" do
11
+ expect(described_class.call(value)).to be(false)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ RSpec.describe Flipper::Expressions::Duration do
2
+ describe "#call" do
3
+ it "raises error with invalid value" do
4
+ expect { described_class.call(false, 'minute') }.to raise_error(ArgumentError)
5
+ end
6
+
7
+ it "raises error with invalid unit" do
8
+ expect { described_class.call(4, 'score') }.to raise_error(ArgumentError)
9
+ end
10
+
11
+ it 'defaults unit to seconds' do
12
+ expect(described_class.call(10)).to eq(10)
13
+ end
14
+
15
+ it "evaluates seconds" do
16
+ expect(described_class.call(10, 'seconds')).to eq(10)
17
+ end
18
+
19
+ it "evaluates minutes" do
20
+ expect(described_class.call(2, 'minutes')).to eq(120)
21
+ end
22
+
23
+ it "evaluates hours" do
24
+ expect(described_class.call(2, 'hours')).to eq(7200)
25
+ end
26
+
27
+ it "evaluates days" do
28
+ expect(described_class.call(2, 'days')).to eq(172_800)
29
+ end
30
+
31
+ it "evaluates weeks" do
32
+ expect(described_class.call(2, 'weeks')).to eq(1_209_600)
33
+ end
34
+
35
+ it "evaluates months" do
36
+ expect(described_class.call(2, 'months')).to eq(5_259_492)
37
+ end
38
+
39
+ it "evaluates years" do
40
+ expect(described_class.call(2, 'years')).to eq(63_113_904)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ RSpec.describe Flipper::Expressions::Equal do
2
+ describe "#call" do
3
+ it "returns true when equal" do
4
+ expect(described_class.call("basic", "basic")).to be(true)
5
+ end
6
+
7
+ it "returns false when not equal" do
8
+ expect(described_class.call("basic", "plus")).to be(false)
9
+ end
10
+
11
+ it "returns false when value evaluates to nil" do
12
+ expect(described_class.call(nil, 1)).to be(false)
13
+ expect(described_class.call(1, nil)).to be(false)
14
+ end
15
+
16
+ it "raises ArgumentError with no arguments" do
17
+ expect { described_class.call }.to raise_error(ArgumentError)
18
+ end
19
+
20
+ it "raises ArgumentError with one argument" do
21
+ expect { described_class.call(10) }.to raise_error(ArgumentError)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ RSpec.describe Flipper::Expressions::GreaterThanOrEqualTo do
2
+ describe "#call" do
3
+ it "returns true when equal" do
4
+ expect(described_class.call(2, 2)).to be(true)
5
+ end
6
+
7
+ it "returns true when greater" do
8
+ expect(described_class.call(2, 1)).to be(true)
9
+ end
10
+
11
+ it "returns false when less" do
12
+ expect(described_class.call(1, 2)).to be(false)
13
+ end
14
+
15
+ it "returns false when value evaluates to nil" do
16
+ expect(described_class.call(nil, 1)).to be(false)
17
+ expect(described_class.call(1, nil)).to be(false)
18
+ end
19
+
20
+ it "raises ArgumentError with no arguments" do
21
+ expect { described_class.call }.to raise_error(ArgumentError)
22
+ end
23
+
24
+ it "raises ArgumentError with one argument" do
25
+ expect { described_class.call(10) }.to raise_error(ArgumentError)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ RSpec.describe Flipper::Expressions::GreaterThan do
2
+ describe "#call" do
3
+ it "returns false when equal" do
4
+ expect(described_class.call(2, 2)).to be(false)
5
+ end
6
+
7
+ it "returns true when greater" do
8
+ expect(described_class.call(2, 1)).to be(true)
9
+ end
10
+
11
+ it "returns false when less" do
12
+ expect(described_class.call(1, 2)).to be(false)
13
+ end
14
+
15
+ it "returns false when value evaluates to nil" do
16
+ expect(described_class.call(nil, 1)).to be(false)
17
+ expect(described_class.call(1, nil)).to be(false)
18
+ end
19
+
20
+ it "raises ArgumentError with no arguments" do
21
+ expect { described_class.call }.to raise_error(ArgumentError)
22
+ end
23
+
24
+ it "raises ArgumentError with one argument" do
25
+ expect { described_class.call(10) }.to raise_error(ArgumentError)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ RSpec.describe Flipper::Expressions::LessThanOrEqualTo do
2
+ describe "#call" do
3
+ it "returns true when equal" do
4
+ expect(described_class.call(2, 2)).to be(true)
5
+ end
6
+
7
+ it "returns true when less" do
8
+ expect(described_class.call(1, 2)).to be(true)
9
+ end
10
+
11
+ it "returns false when greater" do
12
+ expect(described_class.call(2, 1)).to be(false)
13
+ end
14
+
15
+ it "returns false when value evaluates to nil" do
16
+ expect(described_class.call(nil, 1)).to be(false)
17
+ expect(described_class.call(1, nil)).to be(false)
18
+ end
19
+
20
+ it "raises ArgumentError with no arguments" do
21
+ expect { described_class.call }.to raise_error(ArgumentError)
22
+ end
23
+
24
+ it "raises ArgumentError with one argument" do
25
+ expect { described_class.call(10) }.to raise_error(ArgumentError)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ RSpec.describe Flipper::Expressions::LessThan do
2
+ describe "#call" do
3
+ it "returns false when equal" do
4
+ expect(described_class.call(2, 2)).to be(false)
5
+ end
6
+
7
+ it "returns true when less" do
8
+ expect(described_class.call(1, 2)).to be(true)
9
+ end
10
+
11
+ it "returns true when less with args that need evaluation" do
12
+ expect(described_class.call(1, 2)).to be(true)
13
+ end
14
+
15
+ it "returns false when greater" do
16
+ expect(described_class.call(2, 1)).to be(false)
17
+ end
18
+
19
+ it "returns false when value evaluates to nil" do
20
+ expect(described_class.call(nil, 1)).to be(false)
21
+ expect(described_class.call(1, nil)).to be(false)
22
+ end
23
+
24
+ it "raises ArgumentError with no arguments" do
25
+ expect { described_class.call }.to raise_error(ArgumentError)
26
+ end
27
+
28
+ it "raises ArgumentError with one argument" do
29
+ expect { described_class.call(10) }.to raise_error(ArgumentError)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.describe Flipper::Expressions::NotEqual do
2
+ describe "#call" do
3
+ it "returns true when not equal" do
4
+ expect(described_class.call("basic", "plus")).to be(true)
5
+ end
6
+
7
+ it "returns false when equal" do
8
+ expect(described_class.call("basic", "basic")).to be(false)
9
+ end
10
+
11
+ it "raises ArgumentError for more arguments" do
12
+ expect { described_class.call(20, 10, 20).evaluate }.to raise_error(ArgumentError)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ RSpec.describe Flipper::Expressions::Now do
2
+ describe "#call" do
3
+ it "returns current time" do
4
+ expect(described_class.call).to be_within(2).of(Time.now.utc)
5
+ end
6
+
7
+ it "defaults to UTC" do
8
+ expect(described_class.call.zone).to eq("UTC")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ RSpec.describe Flipper::Expressions::Number do
2
+ describe "#call" do
3
+ it "returns Integer for Integer" do
4
+ expect(described_class.call(10)).to be(10)
5
+ end
6
+
7
+ it "returns Float for Float" do
8
+ expect(described_class.call(10.1)).to be(10.1)
9
+ expect(described_class.call(10.0)).to be(10.0)
10
+ end
11
+
12
+ it "returns Integer for String" do
13
+ expect(described_class.call('10')).to be(10)
14
+ end
15
+
16
+ it "returns Float for String" do
17
+ expect(described_class.call('10.0')).to be(10.0)
18
+ expect(described_class.call('10.1')).to be(10.1)
19
+ end
20
+ end
21
+ end
@@ -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,13 @@
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
+ end
13
+ end
@@ -1,65 +1,65 @@
1
1
  RSpec.describe Flipper::FeatureCheckContext do
2
2
  let(:feature_name) { :new_profiles }
3
3
  let(:values) { Flipper::GateValues.new({}) }
4
- let(:thing) { Flipper::Actor.new('5') }
4
+ let(:actor) { Flipper::Actor.new('5') }
5
5
  let(:options) do
6
6
  {
7
7
  feature_name: feature_name,
8
8
  values: values,
9
- thing: thing,
9
+ actors: [actor],
10
10
  }
11
11
  end
12
12
 
13
13
  it 'initializes just fine' do
14
- instance = described_class.new(options)
14
+ instance = described_class.new(**options)
15
15
  expect(instance.feature_name).to eq(feature_name)
16
16
  expect(instance.values).to eq(values)
17
- expect(instance.thing).to eq(thing)
17
+ expect(instance.actors).to eq([actor])
18
18
  end
19
19
 
20
20
  it 'requires feature_name' do
21
21
  options.delete(:feature_name)
22
22
  expect do
23
- described_class.new(options)
24
- end.to raise_error(KeyError)
23
+ described_class.new(**options)
24
+ end.to raise_error(ArgumentError)
25
25
  end
26
26
 
27
27
  it 'requires values' do
28
28
  options.delete(:values)
29
29
  expect do
30
- described_class.new(options)
31
- end.to raise_error(KeyError)
30
+ described_class.new(**options)
31
+ end.to raise_error(ArgumentError)
32
32
  end
33
33
 
34
- it 'requires thing' do
35
- options.delete(:thing)
34
+ it 'requires actors' do
35
+ options.delete(:actors)
36
36
  expect do
37
- described_class.new(options)
38
- end.to raise_error(KeyError)
37
+ described_class.new(**options)
38
+ end.to raise_error(ArgumentError)
39
39
  end
40
40
 
41
41
  it 'knows actors_value' do
42
42
  args = options.merge(values: Flipper::GateValues.new(actors: Set['User;1']))
43
- 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'])
44
44
  end
45
45
 
46
46
  it 'knows groups_value' do
47
47
  args = options.merge(values: Flipper::GateValues.new(groups: Set['admins']))
48
- expect(described_class.new(args).groups_value).to eq(Set['admins'])
48
+ expect(described_class.new(**args).groups_value).to eq(Set['admins'])
49
49
  end
50
50
 
51
51
  it 'knows boolean_value' do
52
- 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)))
53
53
  expect(instance.boolean_value).to eq(true)
54
54
  end
55
55
 
56
56
  it 'knows percentage_of_actors_value' do
57
57
  args = options.merge(values: Flipper::GateValues.new(percentage_of_actors: 14))
58
- 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)
59
59
  end
60
60
 
61
61
  it 'knows percentage_of_time_value' do
62
62
  args = options.merge(values: Flipper::GateValues.new(percentage_of_time: 41))
63
- 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)
64
64
  end
65
65
  end