flipper 0.26.0 → 1.3.6

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +61 -16
  4. data/.github/workflows/examples.yml +55 -18
  5. data/CLAUDE.md +74 -0
  6. data/Changelog.md +1 -486
  7. data/Gemfile +23 -11
  8. data/README.md +31 -27
  9. data/Rakefile +2 -2
  10. data/benchmark/enabled_ips.rb +10 -0
  11. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  12. data/benchmark/enabled_profile.rb +20 -0
  13. data/benchmark/instrumentation_ips.rb +21 -0
  14. data/benchmark/typecast_ips.rb +27 -0
  15. data/docs/images/banner.jpg +0 -0
  16. data/docs/images/flipper_cloud.png +0 -0
  17. data/examples/api/basic.ru +3 -4
  18. data/examples/api/custom_memoized.ru +3 -4
  19. data/examples/api/memoized.ru +3 -4
  20. data/examples/cloud/app.ru +12 -0
  21. data/examples/cloud/backoff_policy.rb +13 -0
  22. data/examples/cloud/basic.rb +22 -0
  23. data/examples/cloud/cloud_setup.rb +20 -0
  24. data/examples/cloud/forked.rb +36 -0
  25. data/examples/cloud/import.rb +17 -0
  26. data/examples/cloud/threaded.rb +33 -0
  27. data/examples/dsl.rb +1 -15
  28. data/examples/enabled_for_actor.rb +4 -2
  29. data/examples/expressions.rb +213 -0
  30. data/examples/mirroring.rb +59 -0
  31. data/examples/strict.rb +18 -0
  32. data/exe/flipper +5 -0
  33. data/flipper-cloud.gemspec +19 -0
  34. data/flipper.gemspec +8 -6
  35. data/lib/flipper/actor.rb +6 -3
  36. data/lib/flipper/adapter.rb +33 -7
  37. data/lib/flipper/adapter_builder.rb +44 -0
  38. data/lib/flipper/adapters/actor_limit.rb +28 -0
  39. data/lib/flipper/adapters/cache_base.rb +143 -0
  40. data/lib/flipper/adapters/dual_write.rb +1 -3
  41. data/lib/flipper/adapters/failover.rb +0 -4
  42. data/lib/flipper/adapters/failsafe.rb +0 -4
  43. data/lib/flipper/adapters/http/client.rb +40 -12
  44. data/lib/flipper/adapters/http/error.rb +2 -2
  45. data/lib/flipper/adapters/http.rb +30 -17
  46. data/lib/flipper/adapters/instrumented.rb +25 -6
  47. data/lib/flipper/adapters/memoizable.rb +33 -21
  48. data/lib/flipper/adapters/memory.rb +81 -46
  49. data/lib/flipper/adapters/operation_logger.rb +17 -78
  50. data/lib/flipper/adapters/poll/poller.rb +2 -125
  51. data/lib/flipper/adapters/poll.rb +20 -3
  52. data/lib/flipper/adapters/pstore.rb +17 -11
  53. data/lib/flipper/adapters/read_only.rb +8 -41
  54. data/lib/flipper/adapters/strict.rb +45 -0
  55. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  56. data/lib/flipper/adapters/sync.rb +0 -4
  57. data/lib/flipper/adapters/wrapper.rb +54 -0
  58. data/lib/flipper/cli.rb +263 -0
  59. data/lib/flipper/cloud/configuration.rb +266 -0
  60. data/lib/flipper/cloud/dsl.rb +27 -0
  61. data/lib/flipper/cloud/message_verifier.rb +95 -0
  62. data/lib/flipper/cloud/middleware.rb +63 -0
  63. data/lib/flipper/cloud/routes.rb +14 -0
  64. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  65. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  66. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  67. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  68. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  69. data/lib/flipper/cloud/telemetry.rb +191 -0
  70. data/lib/flipper/cloud.rb +53 -0
  71. data/lib/flipper/configuration.rb +25 -4
  72. data/lib/flipper/dsl.rb +46 -45
  73. data/lib/flipper/engine.rb +102 -0
  74. data/lib/flipper/errors.rb +3 -3
  75. data/lib/flipper/export.rb +24 -0
  76. data/lib/flipper/exporter.rb +17 -0
  77. data/lib/flipper/exporters/json/export.rb +32 -0
  78. data/lib/flipper/exporters/json/v1.rb +33 -0
  79. data/lib/flipper/expression/builder.rb +73 -0
  80. data/lib/flipper/expression/constant.rb +25 -0
  81. data/lib/flipper/expression.rb +71 -0
  82. data/lib/flipper/expressions/all.rb +9 -0
  83. data/lib/flipper/expressions/any.rb +9 -0
  84. data/lib/flipper/expressions/boolean.rb +9 -0
  85. data/lib/flipper/expressions/comparable.rb +13 -0
  86. data/lib/flipper/expressions/duration.rb +28 -0
  87. data/lib/flipper/expressions/equal.rb +9 -0
  88. data/lib/flipper/expressions/greater_than.rb +9 -0
  89. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  90. data/lib/flipper/expressions/less_than.rb +9 -0
  91. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  92. data/lib/flipper/expressions/not_equal.rb +9 -0
  93. data/lib/flipper/expressions/now.rb +9 -0
  94. data/lib/flipper/expressions/number.rb +9 -0
  95. data/lib/flipper/expressions/percentage.rb +9 -0
  96. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  97. data/lib/flipper/expressions/property.rb +9 -0
  98. data/lib/flipper/expressions/random.rb +9 -0
  99. data/lib/flipper/expressions/string.rb +9 -0
  100. data/lib/flipper/expressions/time.rb +9 -0
  101. data/lib/flipper/feature.rb +94 -26
  102. data/lib/flipper/feature_check_context.rb +10 -6
  103. data/lib/flipper/gate.rb +13 -11
  104. data/lib/flipper/gate_values.rb +5 -18
  105. data/lib/flipper/gates/actor.rb +10 -17
  106. data/lib/flipper/gates/boolean.rb +1 -1
  107. data/lib/flipper/gates/expression.rb +75 -0
  108. data/lib/flipper/gates/group.rb +5 -7
  109. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  110. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  111. data/lib/flipper/identifier.rb +2 -2
  112. data/lib/flipper/instrumentation/log_subscriber.rb +35 -8
  113. data/lib/flipper/instrumentation/statsd.rb +4 -2
  114. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  115. data/lib/flipper/instrumentation/subscriber.rb +8 -5
  116. data/lib/flipper/metadata.rb +8 -1
  117. data/lib/flipper/middleware/memoizer.rb +30 -14
  118. data/lib/flipper/model/active_record.rb +23 -0
  119. data/lib/flipper/poller.rb +118 -0
  120. data/lib/flipper/serializers/gzip.rb +22 -0
  121. data/lib/flipper/serializers/json.rb +17 -0
  122. data/lib/flipper/spec/shared_adapter_specs.rb +105 -63
  123. data/lib/flipper/test/shared_adapter_test.rb +101 -58
  124. data/lib/flipper/test_help.rb +43 -0
  125. data/lib/flipper/typecast.rb +59 -18
  126. data/lib/flipper/types/actor.rb +13 -13
  127. data/lib/flipper/types/group.rb +4 -4
  128. data/lib/flipper/types/percentage.rb +1 -1
  129. data/lib/flipper/version.rb +11 -1
  130. data/lib/flipper.rb +50 -11
  131. data/lib/generators/flipper/setup_generator.rb +68 -0
  132. data/lib/generators/flipper/templates/initializer.rb +45 -0
  133. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  134. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  135. data/lib/generators/flipper/update_generator.rb +35 -0
  136. data/package-lock.json +41 -0
  137. data/package.json +10 -0
  138. data/spec/fixtures/environment.rb +1 -0
  139. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  140. data/spec/flipper/adapter_builder_spec.rb +72 -0
  141. data/spec/flipper/adapter_spec.rb +30 -2
  142. data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
  143. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  144. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  145. data/spec/flipper/adapters/http_spec.rb +138 -55
  146. data/spec/flipper/adapters/instrumented_spec.rb +29 -11
  147. data/spec/flipper/adapters/memoizable_spec.rb +51 -31
  148. data/spec/flipper/adapters/memory_spec.rb +14 -3
  149. data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
  150. data/spec/flipper/adapters/poll_spec.rb +41 -0
  151. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  152. data/spec/flipper/adapters/strict_spec.rb +64 -0
  153. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  154. data/spec/flipper/cli_spec.rb +166 -0
  155. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  156. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  157. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  158. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  159. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  160. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  161. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  162. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  163. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  164. data/spec/flipper/cloud_spec.rb +186 -0
  165. data/spec/flipper/configuration_spec.rb +17 -0
  166. data/spec/flipper/dsl_spec.rb +54 -76
  167. data/spec/flipper/engine_spec.rb +374 -0
  168. data/spec/flipper/export_spec.rb +13 -0
  169. data/spec/flipper/exporter_spec.rb +16 -0
  170. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  171. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  172. data/spec/flipper/expression/builder_spec.rb +248 -0
  173. data/spec/flipper/expression_spec.rb +188 -0
  174. data/spec/flipper/expressions/all_spec.rb +15 -0
  175. data/spec/flipper/expressions/any_spec.rb +15 -0
  176. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  177. data/spec/flipper/expressions/duration_spec.rb +43 -0
  178. data/spec/flipper/expressions/equal_spec.rb +24 -0
  179. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  180. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  181. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  182. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  183. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  184. data/spec/flipper/expressions/now_spec.rb +11 -0
  185. data/spec/flipper/expressions/number_spec.rb +21 -0
  186. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  187. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  188. data/spec/flipper/expressions/property_spec.rb +13 -0
  189. data/spec/flipper/expressions/random_spec.rb +9 -0
  190. data/spec/flipper/expressions/string_spec.rb +11 -0
  191. data/spec/flipper/expressions/time_spec.rb +13 -0
  192. data/spec/flipper/feature_check_context_spec.rb +17 -17
  193. data/spec/flipper/feature_spec.rb +453 -39
  194. data/spec/flipper/gate_values_spec.rb +2 -33
  195. data/spec/flipper/gates/boolean_spec.rb +1 -1
  196. data/spec/flipper/gates/expression_spec.rb +108 -0
  197. data/spec/flipper/gates/group_spec.rb +2 -3
  198. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  199. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  200. data/spec/flipper/identifier_spec.rb +4 -5
  201. data/spec/flipper/instrumentation/log_subscriber_spec.rb +24 -6
  202. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +26 -2
  203. data/spec/flipper/middleware/memoizer_spec.rb +79 -10
  204. data/spec/flipper/model/active_record_spec.rb +72 -0
  205. data/spec/flipper/poller_spec.rb +47 -0
  206. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  207. data/spec/flipper/serializers/json_spec.rb +13 -0
  208. data/spec/flipper/typecast_spec.rb +121 -6
  209. data/spec/flipper/types/actor_spec.rb +63 -46
  210. data/spec/flipper/types/group_spec.rb +2 -2
  211. data/spec/flipper_integration_spec.rb +168 -58
  212. data/spec/flipper_spec.rb +94 -30
  213. data/spec/spec_helper.rb +18 -18
  214. data/spec/support/actor_names.yml +1 -0
  215. data/spec/support/fail_on_output.rb +8 -0
  216. data/spec/support/fake_backoff_policy.rb +15 -0
  217. data/spec/support/skippable.rb +18 -0
  218. data/spec/support/spec_helpers.rb +34 -8
  219. data/test/adapters/actor_limit_test.rb +20 -0
  220. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  221. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  222. data/test_rails/helper.rb +22 -2
  223. data/test_rails/system/test_help_test.rb +52 -0
  224. metadata +203 -20
  225. data/.github/workflows/release.yml +0 -44
  226. data/.tool-versions +0 -1
  227. data/lib/flipper/railtie.rb +0 -47
  228. data/spec/flipper/railtie_spec.rb +0 -109
@@ -0,0 +1,248 @@
1
+ RSpec.describe Flipper::Expression::Builder do
2
+ def build(object)
3
+ Flipper::Expression.build(object)
4
+ end
5
+
6
+ describe "#add" do
7
+ it "converts to Any and adds new expressions" do
8
+ expression = build("something")
9
+ first = Flipper.boolean(true).eq(true)
10
+ second = Flipper.boolean(false).eq(false)
11
+ new_expression = expression.add(first, second)
12
+ expect(new_expression).to eq(build({ Any: ["something", first, second] }))
13
+ end
14
+ end
15
+
16
+ describe "#remove" do
17
+ it "converts to Any and removes any expressions that match" do
18
+ expression = build("something")
19
+ first = Flipper.boolean(true).eq(true)
20
+ second = Flipper.boolean(false).eq(false)
21
+ new_expression = expression.remove(build("something"), first, second)
22
+ expect(new_expression).to eq(build(Any: []))
23
+ end
24
+ end
25
+
26
+ it "can convert to Any" do
27
+ expression = build("something")
28
+ converted = expression.any
29
+ expect(converted).to be_instance_of(Flipper::Expression)
30
+ expect(converted.function).to be(Flipper::Expressions::Any)
31
+ expect(converted.args).to eq([expression])
32
+ end
33
+
34
+ it "can convert to All" do
35
+ expression = build("something")
36
+ converted = expression.all
37
+ expect(converted).to eq(build(All: ["something"]))
38
+ end
39
+
40
+ context "Any" do
41
+ describe "#any" do
42
+ it "returns self" do
43
+ expression = build(Any: [
44
+ Flipper.boolean(true),
45
+ Flipper.string("yep").eq("yep"),
46
+ ])
47
+ expect(expression.any).to be(expression)
48
+ end
49
+ end
50
+
51
+ describe "#add" do
52
+ it "returns new instance with expression added" do
53
+ expression = Flipper.boolean(true)
54
+ other = Flipper.string("yep").eq("yep")
55
+
56
+ result = expression.add(other)
57
+ expect(result.args).to eq([
58
+ Flipper.boolean(true),
59
+ Flipper.string("yep").eq("yep"),
60
+ ])
61
+ end
62
+
63
+ it "returns new instance with many expressions added" do
64
+ expression = Flipper.boolean(true)
65
+ second = Flipper.string("yep").eq("yep")
66
+ third = Flipper.number(1).lte(20)
67
+
68
+ result = expression.add(second, third)
69
+ expect(result.args).to eq([
70
+ Flipper.boolean(true),
71
+ Flipper.string("yep").eq("yep"),
72
+ Flipper.number(1).lte(20),
73
+ ])
74
+ end
75
+
76
+ it "returns new instance with array of expressions added" do
77
+ expression = Flipper.boolean(true)
78
+ second = Flipper.string("yep").eq("yep")
79
+ third = Flipper.number(1).lte(20)
80
+
81
+ result = expression.add([second, third])
82
+ expect(result.args).to eq([
83
+ Flipper.boolean(true),
84
+ Flipper.string("yep").eq("yep"),
85
+ Flipper.number(1).lte(20),
86
+ ])
87
+ end
88
+ end
89
+
90
+ describe "#remove" do
91
+ it "returns new instance with expression removed" do
92
+ first = Flipper.boolean(true)
93
+ second = Flipper.string("yep").eq("yep")
94
+ third = Flipper.number(1).lte(20)
95
+ expression = Flipper.any([first, second, third])
96
+
97
+ result = expression.remove(second)
98
+ expect(expression.args).to eq([first, second, third])
99
+ expect(result.args).to eq([first, third])
100
+ end
101
+
102
+ it "returns new instance with many expressions removed" do
103
+ first = Flipper.boolean(true)
104
+ second = Flipper.string("yep").eq("yep")
105
+ third = Flipper.number(1).lte(20)
106
+ expression = Flipper.any([first, second, third])
107
+
108
+ result = expression.remove(second, third)
109
+ expect(expression.args).to eq([first, second, third])
110
+ expect(result.args).to eq([first])
111
+ end
112
+
113
+ it "returns new instance with array of expressions removed" do
114
+ first = Flipper.boolean(true)
115
+ second = Flipper.string("yep").eq("yep")
116
+ third = Flipper.number(1).lte(20)
117
+ expression = Flipper.any([first, second, third])
118
+
119
+ result = expression.remove([second, third])
120
+ expect(expression.args).to eq([first, second, third])
121
+ expect(result.args).to eq([first])
122
+ end
123
+ end
124
+ end
125
+
126
+ [
127
+ [2, 3, "equal", "eq", :Equal],
128
+ [2, 3, "not_equal", "neq", :NotEqual],
129
+ [2, 3, "greater_than", "gt", :GreaterThan],
130
+ [2, 3, "greater_than_or_equal_to", "gte", :GreaterThanOrEqualTo],
131
+ [2, 3, "greater_than_or_equal_to", "greater_than_or_equal", :GreaterThanOrEqualTo],
132
+ [2, 3, "less_than", "lt", :LessThan],
133
+ [2, 3, "less_than_or_equal_to", "lte", :LessThanOrEqualTo],
134
+ [2, 3, "less_than_or_equal_to", "less_than_or_equal", :LessThanOrEqualTo],
135
+ ].each do |(left, right, method_name, shortcut_name, function)|
136
+ it "can convert to #{function}" do
137
+ expression = build(left)
138
+ other = build(right)
139
+ converted = expression.send(method_name, other)
140
+ expect(converted).to eq(build({ function => [ left, right] }))
141
+ end
142
+
143
+ it "can convert to #{function} using #{shortcut_name}" do
144
+ expression = build(left)
145
+ other = build(right)
146
+ converted = expression.send(shortcut_name, other)
147
+ expect(converted).to eq(build({ function => [ left, right] }))
148
+ end
149
+
150
+ it "builds args into expressions when converting to #{function}" do
151
+ expression = build(left)
152
+ other = Flipper.property(:age)
153
+ converted = expression.send(method_name, other.value)
154
+ expect(converted).to eq(build({ function => [ left, other.value] }))
155
+ end
156
+ end
157
+
158
+ it "can convert to PercentageOfActors" do
159
+ expression = Flipper.constant("User;1").percentage_of_actors(40)
160
+ expect(expression).to eq(build({ PercentageOfActors: [ "User;1", 40 ] }))
161
+ end
162
+
163
+ context "All" do
164
+ describe "#all" do
165
+ it "returns self" do
166
+ expression = Flipper.all([
167
+ Flipper.boolean(true),
168
+ Flipper.string("yep").eq("yep"),
169
+ ])
170
+ expect(expression.all).to be(expression)
171
+ end
172
+ end
173
+
174
+ describe "#add" do
175
+ it "returns new instance with expression added" do
176
+ expression = Flipper.all([Flipper.boolean(true)])
177
+ other = Flipper.string("yep").eq("yep")
178
+
179
+ result = expression.add(other)
180
+ expect(result.args).to eq([
181
+ Flipper.boolean(true),
182
+ Flipper.string("yep").eq("yep"),
183
+ ])
184
+ end
185
+
186
+ it "returns new instance with many expressions added" do
187
+ expression = Flipper.all([Flipper.boolean(true)])
188
+ second = Flipper.string("yep").eq("yep")
189
+ third = Flipper.number(1).lte(20)
190
+
191
+ result = expression.add(second, third)
192
+ expect(result.args).to eq([
193
+ Flipper.boolean(true),
194
+ Flipper.string("yep").eq("yep"),
195
+ Flipper.number(1).lte(20),
196
+ ])
197
+ end
198
+
199
+ it "returns new instance with array of expressions added" do
200
+ expression = Flipper.all([Flipper.boolean(true)])
201
+ second = Flipper.string("yep").eq("yep")
202
+ third = Flipper.number(1).lte(20)
203
+
204
+ result = expression.add([second, third])
205
+ expect(result.args).to eq([
206
+ Flipper.boolean(true),
207
+ Flipper.string("yep").eq("yep"),
208
+ Flipper.number(1).lte(20),
209
+ ])
210
+ end
211
+ end
212
+
213
+ describe "#remove" do
214
+ it "returns new instance with expression removed" do
215
+ first = Flipper.boolean(true)
216
+ second = Flipper.string("yep").eq("yep")
217
+ third = Flipper.number(1).lte(20)
218
+ expression = Flipper.all([first, second, third])
219
+
220
+ result = expression.remove(second)
221
+ expect(expression.args).to eq([first, second, third])
222
+ expect(result.args).to eq([first, third])
223
+ end
224
+
225
+ it "returns new instance with many expressions removed" do
226
+ first = Flipper.boolean(true)
227
+ second = Flipper.string("yep").eq("yep")
228
+ third = Flipper.number(1).lte(20)
229
+ expression = Flipper.all([first, second, third])
230
+
231
+ result = expression.remove(second, third)
232
+ expect(expression.args).to eq([first, second, third])
233
+ expect(result.args).to eq([first])
234
+ end
235
+
236
+ it "returns new instance with array of expressions removed" do
237
+ first = Flipper.boolean(true)
238
+ second = Flipper.string("yep").eq("yep")
239
+ third = Flipper.number(1).lte(20)
240
+ expression = Flipper.all([first, second, third])
241
+
242
+ result = expression.remove([second, third])
243
+ expect(expression.args).to eq([first, second, third])
244
+ expect(result.args).to eq([first])
245
+ end
246
+ end
247
+ end
248
+ end
@@ -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