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
@@ -7,25 +7,32 @@ RSpec.describe Flipper do
7
7
  let(:admin_group) { flipper.group(:admins) }
8
8
  let(:dev_group) { flipper.group(:devs) }
9
9
 
10
- let(:admin_thing) do
11
- double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false
10
+ let(:admin_actor) do
11
+ double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false, flipper_properties: {"admin" => true, "dev" => false}
12
12
  end
13
- let(:dev_thing) do
14
- double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true
13
+ let(:dev_actor) do
14
+ double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true, flipper_properties: {"admin" => false, "dev" => true}
15
15
  end
16
16
 
17
- let(:admin_truthy_thing) do
18
- double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false
17
+ let(:admin_truthy_actor) do
18
+ double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false, flipper_properties: {"admin" => "true-ish", "dev" => false}
19
19
  end
20
- let(:admin_falsey_thing) do
21
- double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false
20
+ let(:admin_falsey_actor) do
21
+ double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false, flipper_properties: {"admin" => nil, "dev" => false}
22
+ end
23
+
24
+ let(:basic_plan_actor) do
25
+ double 'Non Flipper Thing', flipper_id: 1, flipper_properties: {"plan" => "basic"}
26
+ end
27
+ let(:premium_plan_actor) do
28
+ double 'Non Flipper Thing', flipper_id: 10, flipper_properties: {"plan" => "premium"}
22
29
  end
23
30
 
24
31
  let(:pitt) { Flipper::Actor.new(1) }
25
32
  let(:clooney) { Flipper::Actor.new(10) }
26
33
 
27
- let(:five_percent_of_actors) { flipper.actors(5) }
28
- let(:five_percent_of_time) { flipper.time(5) }
34
+ let(:five_percent_of_actors) { Flipper::Types::PercentageOfActors.new(5) }
35
+ let(:five_percent_of_time) { Flipper::Types::PercentageOfTime.new(5) }
29
36
 
30
37
  before do
31
38
  described_class.register(:admins, &:admin?)
@@ -60,20 +67,22 @@ RSpec.describe Flipper do
60
67
  expect(@result).to eq(true)
61
68
  end
62
69
 
63
- it 'enables feature for non flipper thing in group' do
64
- expect(feature.enabled?(admin_thing)).to eq(true)
70
+ it 'enables feature for non flipper actor in group' do
71
+ expect(feature.enabled?(admin_actor)).to eq(true)
65
72
  end
66
73
 
67
- it 'does not enable feature for non flipper thing in other group' do
68
- expect(feature.enabled?(dev_thing)).to eq(false)
74
+ it 'does not enable feature for non flipper actor in other group' do
75
+ expect(feature.enabled?(dev_actor)).to eq(false)
69
76
  end
70
77
 
71
78
  it 'enables feature for flipper actor in group' do
72
- expect(feature.enabled?(flipper.actor(admin_thing))).to eq(true)
79
+ expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(true)
80
+ expect(feature.enabled?(admin_actor)).to eq(true)
73
81
  end
74
82
 
75
83
  it 'does not enable for flipper actor not in group' do
76
- expect(feature.enabled?(flipper.actor(dev_thing))).to eq(false)
84
+ expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(false)
85
+ expect(feature.enabled?(dev_actor)).to eq(false)
77
86
  end
78
87
 
79
88
  it 'does not enable feature for all' do
@@ -118,8 +127,8 @@ RSpec.describe Flipper do
118
127
 
119
128
  it 'enables feature for actor within percentage' do
120
129
  enabled = (1..100).select do |i|
121
- thing = Flipper::Actor.new(i)
122
- feature.enabled?(thing)
130
+ actor = Flipper::Actor.new(i)
131
+ feature.enabled?(actor)
123
132
  end.size
124
133
 
125
134
  expect(enabled).to be_within(2).of(5)
@@ -141,8 +150,8 @@ RSpec.describe Flipper do
141
150
 
142
151
  it 'enables feature for actor within percentage' do
143
152
  enabled = (1..100).select do |i|
144
- thing = Flipper::Actor.new(i)
145
- feature.enabled?(thing)
153
+ actor = Flipper::Actor.new(i)
154
+ feature.enabled?(actor)
146
155
  end.size
147
156
 
148
157
  expect(enabled).to be_within(2).of(5)
@@ -180,10 +189,10 @@ RSpec.describe Flipper do
180
189
 
181
190
  context 'with argument that has no gate' do
182
191
  it 'raises error' do
183
- thing = Object.new
192
+ actor = Object.new
184
193
  expect do
185
- feature.enable(thing)
186
- end.to raise_error(Flipper::GateNotFound, "Could not find gate for #{thing.inspect}")
194
+ feature.enable(actor)
195
+ end.to raise_error(Flipper::GateNotFound, "Could not find gate for #{actor.inspect}")
187
196
  end
188
197
  end
189
198
  end
@@ -215,13 +224,13 @@ RSpec.describe Flipper do
215
224
  end
216
225
 
217
226
  it 'disables actor in group' do
218
- expect(feature.enabled?(admin_thing)).to eq(false)
227
+ expect(feature.enabled?(admin_actor)).to eq(false)
219
228
  end
220
229
 
221
230
  it 'disables actor in percentage of actors' do
222
231
  enabled = (1..100).select do |i|
223
- thing = Flipper::Actor.new(i)
224
- feature.enabled?(thing)
232
+ actor = Flipper::Actor.new(i)
233
+ feature.enabled?(actor)
225
234
  end.size
226
235
 
227
236
  expect(enabled).to be(0)
@@ -247,20 +256,22 @@ RSpec.describe Flipper do
247
256
  expect(@result).to eq(true)
248
257
  end
249
258
 
250
- it 'disables the feature for non flipper thing in the group' do
251
- expect(feature.enabled?(admin_thing)).to eq(false)
259
+ it 'disables the feature for non flipper actor in the group' do
260
+ expect(feature.enabled?(admin_actor)).to eq(false)
252
261
  end
253
262
 
254
- it 'does not disable feature for non flipper thing in other groups' do
255
- expect(feature.enabled?(dev_thing)).to eq(true)
263
+ it 'does not disable feature for non flipper actor in other groups' do
264
+ expect(feature.enabled?(dev_actor)).to eq(true)
256
265
  end
257
266
 
258
267
  it 'disables feature for flipper actor in group' do
259
- expect(feature.enabled?(flipper.actor(admin_thing))).to eq(false)
268
+ expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(false)
269
+ expect(feature.enabled?(admin_actor)).to eq(false)
260
270
  end
261
271
 
262
272
  it 'does not disable feature for flipper actor in other groups' do
263
- expect(feature.enabled?(flipper.actor(dev_thing))).to eq(true)
273
+ expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(true)
274
+ expect(feature.enabled?(dev_actor)).to eq(true)
264
275
  end
265
276
 
266
277
  it 'adds feature to set of features' do
@@ -294,7 +305,7 @@ RSpec.describe Flipper do
294
305
 
295
306
  context 'with a percentage of actors' do
296
307
  before do
297
- @result = feature.disable(flipper.actors(0))
308
+ @result = feature.disable(Flipper::Types::PercentageOfActors.new(0))
298
309
  end
299
310
 
300
311
  it 'returns true' do
@@ -303,8 +314,8 @@ RSpec.describe Flipper do
303
314
 
304
315
  it 'disables feature' do
305
316
  enabled = (1..100).select do |i|
306
- thing = Flipper::Actor.new(i)
307
- feature.enabled?(thing)
317
+ actor = Flipper::Actor.new(i)
318
+ feature.enabled?(actor)
308
319
  end.size
309
320
 
310
321
  expect(enabled).to be(0)
@@ -318,7 +329,7 @@ RSpec.describe Flipper do
318
329
  context 'with a percentage of time' do
319
330
  before do
320
331
  @gate = feature.gate(:percentage_of_time)
321
- @result = feature.disable(flipper.time(0))
332
+ @result = feature.disable(Flipper::Types::PercentageOfTime.new(0))
322
333
  end
323
334
 
324
335
  it 'returns true' do
@@ -342,10 +353,10 @@ RSpec.describe Flipper do
342
353
 
343
354
  context 'with argument that has no gate' do
344
355
  it 'raises error' do
345
- thing = Object.new
356
+ actor = Object.new
346
357
  expect do
347
- feature.disable(thing)
348
- end.to raise_error(Flipper::GateNotFound, "Could not find gate for #{thing.inspect}")
358
+ feature.disable(actor)
359
+ end.to raise_error(Flipper::GateNotFound, "Could not find gate for #{actor.inspect}")
349
360
  end
350
361
  end
351
362
  end
@@ -373,23 +384,29 @@ RSpec.describe Flipper do
373
384
  end
374
385
 
375
386
  it 'returns true' do
376
- expect(feature.enabled?(flipper.actor(admin_thing))).to eq(true)
377
- expect(feature.enabled?(admin_thing)).to eq(true)
387
+ expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(true)
388
+ expect(feature.enabled?(admin_actor)).to eq(true)
378
389
  end
379
390
 
380
391
  it 'returns true for truthy block values' do
381
- expect(feature.enabled?(flipper.actor(admin_truthy_thing))).to eq(true)
392
+ expect(feature.enabled?(Flipper::Types::Actor.new(admin_truthy_actor))).to eq(true)
393
+ expect(feature.enabled?(admin_truthy_actor)).to eq(true)
394
+ end
395
+
396
+ it 'returns true if any actor is in enabled group' do
397
+ expect(feature.enabled?(dev_actor, admin_actor)).to be(true)
382
398
  end
383
399
  end
384
400
 
385
401
  context 'for actor in disabled group' do
386
402
  it 'returns false' do
387
- expect(feature.enabled?(flipper.actor(dev_thing))).to eq(false)
388
- expect(feature.enabled?(dev_thing)).to eq(false)
403
+ expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(false)
404
+ expect(feature.enabled?(dev_actor)).to eq(false)
389
405
  end
390
406
 
391
407
  it 'returns false for falsey block values' do
392
- expect(feature.enabled?(flipper.actor(admin_falsey_thing))).to eq(false)
408
+ expect(feature.enabled?(Flipper::Types::Actor.new(admin_falsey_actor))).to eq(false)
409
+ expect(feature.enabled?(admin_falsey_actor)).to eq(false)
393
410
  end
394
411
  end
395
412
 
@@ -408,6 +425,10 @@ RSpec.describe Flipper do
408
425
  expect(feature.enabled?(clooney)).to eq(false)
409
426
  end
410
427
 
428
+ it 'returns false if all actors are disabled' do
429
+ expect(feature.enabled?(clooney, pitt)).to be(false)
430
+ end
431
+
411
432
  it 'returns true if boolean enabled' do
412
433
  feature.enable
413
434
  expect(feature.enabled?(clooney)).to eq(true)
@@ -428,7 +449,7 @@ RSpec.describe Flipper do
428
449
  expect(feature.enabled?).to eq(true)
429
450
  expect(feature.enabled?(nil)).to eq(true)
430
451
  expect(feature.enabled?(pitt)).to eq(true)
431
- expect(feature.enabled?(admin_thing)).to eq(true)
452
+ expect(feature.enabled?(admin_actor)).to eq(true)
432
453
  end
433
454
  end
434
455
 
@@ -446,7 +467,7 @@ RSpec.describe Flipper do
446
467
  expect(feature.enabled?).to eq(true)
447
468
  expect(feature.enabled?(nil)).to eq(true)
448
469
  expect(feature.enabled?(pitt)).to eq(true)
449
- expect(feature.enabled?(admin_thing)).to eq(true)
470
+ expect(feature.enabled?(admin_actor)).to eq(true)
450
471
  end
451
472
  end
452
473
 
@@ -463,7 +484,7 @@ RSpec.describe Flipper do
463
484
  expect(feature.enabled?).to eq(false)
464
485
  expect(feature.enabled?(nil)).to eq(false)
465
486
  expect(feature.enabled?(pitt)).to eq(false)
466
- expect(feature.enabled?(admin_thing)).to eq(false)
487
+ expect(feature.enabled?(admin_actor)).to eq(false)
467
488
  end
468
489
 
469
490
  it 'returns true if boolean enabled' do
@@ -471,7 +492,7 @@ RSpec.describe Flipper do
471
492
  expect(feature.enabled?).to eq(true)
472
493
  expect(feature.enabled?(nil)).to eq(true)
473
494
  expect(feature.enabled?(pitt)).to eq(true)
474
- expect(feature.enabled?(admin_thing)).to eq(true)
495
+ expect(feature.enabled?(admin_actor)).to eq(true)
475
496
  end
476
497
  end
477
498
 
@@ -488,7 +509,7 @@ RSpec.describe Flipper do
488
509
  expect(feature.enabled?).to eq(false)
489
510
  expect(feature.enabled?(nil)).to eq(false)
490
511
  expect(feature.enabled?(pitt)).to eq(false)
491
- expect(feature.enabled?(admin_thing)).to eq(false)
512
+ expect(feature.enabled?(admin_actor)).to eq(false)
492
513
  end
493
514
 
494
515
  it 'returns true if boolean enabled' do
@@ -496,27 +517,31 @@ RSpec.describe Flipper do
496
517
  expect(feature.enabled?).to eq(true)
497
518
  expect(feature.enabled?(nil)).to eq(true)
498
519
  expect(feature.enabled?(pitt)).to eq(true)
499
- expect(feature.enabled?(admin_thing)).to eq(true)
520
+ expect(feature.enabled?(admin_actor)).to eq(true)
500
521
  end
501
522
  end
502
523
 
503
- context 'for a non flipper thing' do
524
+ context 'for a non flipper actor' do
504
525
  before do
505
526
  feature.enable admin_group
506
527
  end
507
528
 
508
529
  it 'returns true if in enabled group' do
509
- expect(feature.enabled?(admin_thing)).to eq(true)
530
+ expect(feature.enabled?(admin_actor)).to eq(true)
510
531
  end
511
532
 
512
533
  it 'returns false if not in enabled group' do
513
- expect(feature.enabled?(dev_thing)).to eq(false)
534
+ expect(feature.enabled?(dev_actor)).to eq(false)
535
+ end
536
+
537
+ it 'retruns true if any actor is true' do
538
+ expect(feature.enabled?(admin_actor, dev_actor)).to eq(true)
514
539
  end
515
540
 
516
541
  it 'returns true if boolean enabled' do
517
542
  feature.enable
518
- expect(feature.enabled?(admin_thing)).to eq(true)
519
- expect(feature.enabled?(dev_thing)).to eq(true)
543
+ expect(feature.enabled?(admin_actor)).to eq(true)
544
+ expect(feature.enabled?(dev_actor)).to eq(true)
520
545
  end
521
546
  end
522
547
  end
@@ -530,11 +555,96 @@ RSpec.describe Flipper do
530
555
  end
531
556
 
532
557
  it 'enables feature for object in enabled group' do
533
- expect(feature.enabled?(admin_thing)).to eq(true)
558
+ expect(feature.enabled?(admin_actor)).to eq(true)
534
559
  end
535
560
 
536
561
  it 'does not enable feature for object in not enabled group' do
537
- expect(feature.enabled?(dev_thing)).to eq(false)
562
+ expect(feature.enabled?(dev_actor)).to eq(false)
563
+ end
564
+ end
565
+
566
+ context "for expression" do
567
+ it "works" do
568
+ feature.enable Flipper.property(:plan).eq("basic")
569
+
570
+ expect(feature.enabled?).to be(false)
571
+ expect(feature.enabled?(basic_plan_actor)).to be(true)
572
+ expect(feature.enabled?(premium_plan_actor)).to be(false)
573
+ expect(feature.enabled?(admin_actor)).to be(false)
574
+ end
575
+
576
+ it "works for true expression with no actor" do
577
+ feature.enable Flipper.boolean(true)
578
+ expect(feature.enabled?).to be(true)
579
+ end
580
+
581
+ it "works for multiple actors" do
582
+ feature.enable Flipper.property(:plan).eq("basic")
583
+
584
+ expect(feature.enabled?(basic_plan_actor, premium_plan_actor)).to be(true)
585
+ expect(feature.enabled?(premium_plan_actor, basic_plan_actor)).to be(true)
586
+ expect(feature.enabled?(premium_plan_actor, admin_actor)).to be(false)
587
+ end
588
+ end
589
+
590
+ context "for Any" do
591
+ it "works" do
592
+ expression = Flipper.any(
593
+ Flipper.property(:plan).eq("basic"),
594
+ Flipper.property(:plan).eq("plus"),
595
+ )
596
+ feature.enable expression
597
+
598
+ expect(feature.enabled?(basic_plan_actor)).to be(true)
599
+ expect(feature.enabled?(premium_plan_actor)).to be(false)
600
+ end
601
+ end
602
+
603
+ context "for All" do
604
+ it "works" do
605
+ true_actor = Flipper::Actor.new("User;1", {
606
+ "plan" => "basic",
607
+ "age" => 21,
608
+ })
609
+ false_actor = Flipper::Actor.new("User;1", {
610
+ "plan" => "basic",
611
+ "age" => 20,
612
+ })
613
+ expression = Flipper.all(
614
+ Flipper.property(:plan).eq("basic"),
615
+ Flipper.property(:age).eq(21)
616
+ )
617
+ feature.enable expression
618
+
619
+ expect(feature.enabled?(true_actor)).to be(true)
620
+ expect(feature.enabled?(false_actor)).to be(false)
621
+ end
622
+
623
+ it "works when nested" do
624
+ admin_actor = Flipper::Actor.new("User;1", {
625
+ "admin" => true,
626
+ })
627
+ true_actor = Flipper::Actor.new("User;1", {
628
+ "plan" => "basic",
629
+ "age" => 21,
630
+ })
631
+ false_actor = Flipper::Actor.new("User;1", {
632
+ "plan" => "basic",
633
+ "age" => 20,
634
+ })
635
+ expression = Flipper.any(
636
+ Flipper.property(:admin).eq(true),
637
+ Flipper.all(
638
+ Flipper.property(:plan).eq("basic"),
639
+ Flipper.property(:age).eq(21)
640
+ )
641
+ )
642
+
643
+ feature.enable expression
644
+
645
+ expect(feature.enabled?(admin_actor)).to be(true)
646
+ expect(feature.enabled?(true_actor)).to be(true)
647
+ expect(feature.enabled?(false_actor)).to be(false)
538
648
  end
539
649
  end
540
650
  end
data/spec/flipper_spec.rb CHANGED
@@ -64,7 +64,12 @@ RSpec.describe Flipper do
64
64
 
65
65
  describe "delegation to instance" do
66
66
  let(:group) { Flipper::Types::Group.new(:admins) }
67
- let(:actor) { Flipper::Actor.new("1") }
67
+ let(:actor) {
68
+ Flipper::Actor.new("1", {
69
+ "plan" => "basic",
70
+ })
71
+ }
72
+ let(:expression) { Flipper.property(:plan).eq("basic") }
68
73
 
69
74
  before do
70
75
  described_class.configure do |config|
@@ -88,12 +93,35 @@ RSpec.describe Flipper do
88
93
  expect(described_class.instance.enabled?(:search)).to be(false)
89
94
  end
90
95
 
91
- it 'delegates bool to instance' do
92
- expect(described_class.bool).to eq(described_class.instance.bool)
96
+ it 'delegates expression to instance' do
97
+ expect(described_class.expression(:search)).to be(nil)
98
+
99
+ expression = Flipper.property(:plan).eq("basic")
100
+ Flipper.instance.enable_expression :search, expression
101
+
102
+ expect(described_class.expression(:search)).to eq(expression)
93
103
  end
94
104
 
95
- it 'delegates boolean to instance' do
96
- expect(described_class.boolean).to eq(described_class.instance.boolean)
105
+ it 'delegates enable_expression to instance' do
106
+ described_class.enable_expression(:search, expression)
107
+ expect(described_class.instance.enabled?(:search, actor)).to be(true)
108
+ end
109
+
110
+ it 'delegates disable_expression to instance' do
111
+ described_class.disable_expression(:search)
112
+ expect(described_class.instance.enabled?(:search, actor)).to be(false)
113
+ end
114
+
115
+ it 'delegates add_expression to instance' do
116
+ described_class.add_expression(:search, expression)
117
+ expect(described_class.instance.enabled?(:search, actor)).to be(true)
118
+ end
119
+
120
+ it 'delegates remove_expression to instance' do
121
+ described_class.enable_expression(:search, Flipper.any(expression))
122
+ expect(described_class.instance.enabled?(:search, actor)).to be(true)
123
+ described_class.remove_expression(:search, expression)
124
+ expect(described_class.instance.enabled?(:search, actor)).to be(false)
97
125
  end
98
126
 
99
127
  it 'delegates enable_actor to instance' do
@@ -106,10 +134,6 @@ RSpec.describe Flipper do
106
134
  expect(described_class.instance.enabled?(:search, actor)).to be(false)
107
135
  end
108
136
 
109
- it 'delegates actor to instance' do
110
- expect(described_class.actor(actor)).to eq(described_class.instance.actor(actor))
111
- end
112
-
113
137
  it 'delegates enable_group to instance' do
114
138
  described_class.enable_group(:search, group)
115
139
  expect(described_class.instance[:search].enabled_groups).to include(group)
@@ -130,15 +154,6 @@ RSpec.describe Flipper do
130
154
  expect(described_class.instance[:search].percentage_of_actors_value).to be(0)
131
155
  end
132
156
 
133
- it 'delegates actors to instance' do
134
- expect(described_class.actors(5)).to eq(described_class.instance.actors(5))
135
- end
136
-
137
- it 'delegates percentage_of_actors to instance' do
138
- expected = described_class.instance.percentage_of_actors(5)
139
- expect(described_class.percentage_of_actors(5)).to eq(expected)
140
- end
141
-
142
157
  it 'delegates enable_percentage_of_time to instance' do
143
158
  described_class.enable_percentage_of_time(:search, 5)
144
159
  expect(described_class.instance[:search].percentage_of_time_value).to be(5)
@@ -149,15 +164,6 @@ RSpec.describe Flipper do
149
164
  expect(described_class.instance[:search].percentage_of_time_value).to be(0)
150
165
  end
151
166
 
152
- it 'delegates time to instance' do
153
- expect(described_class.time(56)).to eq(described_class.instance.time(56))
154
- end
155
-
156
- it 'delegates percentage_of_time to instance' do
157
- expected = described_class.instance.percentage_of_time(56)
158
- expect(described_class.percentage_of_time(56)).to eq(expected)
159
- end
160
-
161
167
  it 'delegates features to instance' do
162
168
  described_class.instance.add(:search)
163
169
  expect(described_class.features).to eq(described_class.instance.features)
@@ -202,6 +208,12 @@ RSpec.describe Flipper do
202
208
  expect(described_class.enabled?(:search)).to be(true)
203
209
  end
204
210
 
211
+ it 'delegates export to instance' do
212
+ described_class.enable(:search)
213
+ expect(described_class.export).to eq(described_class.adapter.export)
214
+ expect(described_class.export(format: :json)).to eq(described_class.adapter.export(format: :json))
215
+ end
216
+
205
217
  it 'delegates adapter to instance' do
206
218
  expect(described_class.adapter).to eq(described_class.instance.adapter)
207
219
  end
@@ -216,16 +228,20 @@ RSpec.describe Flipper do
216
228
  expect(described_class.memoizing?).to eq(described_class.adapter.memoizing?)
217
229
  end
218
230
 
231
+ it 'delegates read_only? to instance' do
232
+ expect(described_class.read_only?).to eq(described_class.adapter.read_only?)
233
+ end
234
+
219
235
  it 'delegates sync stuff to instance and does nothing' do
220
236
  expect(described_class.sync).to be(nil)
221
237
  expect(described_class.sync_secret).to be(nil)
222
238
  end
223
239
 
224
240
  it 'delegates sync stuff to instance for Flipper::Cloud' do
225
- stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").
241
+ stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
226
242
  with({
227
243
  headers: {
228
- 'Flipper-Cloud-Token'=>'asdf',
244
+ 'flipper-cloud-token'=>'asdf',
229
245
  },
230
246
  }).to_return(status: 200, body: '{"features": {}}', headers: {})
231
247
  cloud_configuration = Flipper::Cloud::Configuration.new({
@@ -297,7 +313,7 @@ RSpec.describe Flipper do
297
313
 
298
314
  describe '.group_exists' do
299
315
  it 'returns true if the group is already created' do
300
- group = described_class.register('admins', &:admin?)
316
+ described_class.register('admins', &:admin?)
301
317
  expect(described_class.group_exists?(:admins)).to eq(true)
302
318
  end
303
319
 
@@ -350,4 +366,52 @@ RSpec.describe Flipper do
350
366
  expect(described_class.instance_variable_get('@groups_registry')).to eq(registry)
351
367
  end
352
368
  end
369
+
370
+ describe ".constant" do
371
+ it "returns Flipper::Expression::Constant instance" do
372
+ expect(described_class.constant(false)).to eq(Flipper::Expression::Constant.new(false))
373
+ expect(described_class.constant("string")).to eq(Flipper::Expression::Constant.new("string"))
374
+ end
375
+ end
376
+
377
+ describe ".property" do
378
+ it "returns Flipper::Expressions::Property expression" do
379
+ expect(Flipper.property("name")).to eq(Flipper::Expression.build(Property: "name"))
380
+ end
381
+ end
382
+
383
+ describe ".boolean" do
384
+ it "returns Flipper::Expressions::Boolean expression" do
385
+ expect(described_class.boolean(true)).to eq(Flipper::Expression.build(Boolean: true))
386
+ expect(described_class.boolean(false)).to eq(Flipper::Expression.build(Boolean: false))
387
+ end
388
+ end
389
+
390
+ describe ".random" do
391
+ it "returns Flipper::Expressions::Random expression" do
392
+ expect(Flipper.random(100)).to eq(Flipper::Expression.build(Random: 100))
393
+ end
394
+ end
395
+
396
+ describe ".any" do
397
+ let(:age_expression) { Flipper.property(:age).gte(21) }
398
+ let(:plan_expression) { Flipper.property(:plan).eq("basic") }
399
+
400
+ it "returns Flipper::Expressions::Any instance" do
401
+ expect(Flipper.any(age_expression, plan_expression)).to eq(
402
+ Flipper::Expression.build({Any: [age_expression, plan_expression]})
403
+ )
404
+ end
405
+ end
406
+
407
+ describe ".all" do
408
+ let(:age_expression) { Flipper.property(:age).gte(21) }
409
+ let(:plan_expression) { Flipper.property(:plan).eq("basic") }
410
+
411
+ it "returns Flipper::Expressions::All instance" do
412
+ expect(Flipper.all(age_expression, plan_expression)).to eq(
413
+ Flipper::Expression.build({All: [age_expression, plan_expression]})
414
+ )
415
+ end
416
+ end
353
417
  end
data/spec/spec_helper.rb CHANGED
@@ -2,14 +2,17 @@ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
2
 
3
3
  require 'pp'
4
4
  require 'pathname'
5
- FlipperRoot = Pathname(__FILE__).dirname.join('..').expand_path
6
-
7
- require 'rubygems'
8
- require 'bundler'
5
+ require 'bundler/setup'
9
6
 
10
- Bundler.setup(:default)
7
+ require 'warning'
8
+ Warning.ignore(/lib\/statsd/)
9
+ Warning.ignore(/lib\/debug\//)
10
+ Warning.ignore(/lib\/ice_age\//)
11
+ Warning.ignore(/lib\/moneta\//)
12
+ Warning.ignore(/lib\/mongo\//)
11
13
 
12
14
  require 'debug'
15
+ require 'statsd'
13
16
  require 'webmock/rspec'
14
17
  WebMock.disable_net_connect!(allow_localhost: true)
15
18
 
@@ -17,12 +20,21 @@ require 'flipper'
17
20
  require 'flipper/api'
18
21
  require 'flipper/spec/shared_adapter_specs'
19
22
  require 'flipper/ui'
23
+ require 'flipper/test_help'
20
24
 
25
+ FlipperRoot = Pathname(__FILE__).dirname.join('..').expand_path
21
26
  Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }
22
27
 
28
+ # Disable telemetry logging in specs.
29
+ ENV["FLIPPER_CLOUD_LOGGING_ENABLED"] = "false"
30
+
23
31
  RSpec.configure do |config|
24
32
  config.before(:example) do
25
- Flipper::Adapters::Poll::Poller.reset if defined?(Flipper::Adapters::Poll::Poller)
33
+ # default stub for telemetry
34
+ stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
35
+ to_return(status: 200, body: "", headers: {})
36
+ Flipper::Cloud::Telemetry.reset if defined?(Flipper::Cloud::Telemetry) && Flipper::Cloud::Telemetry.respond_to?(:reset)
37
+ Flipper::Poller.reset if defined?(Flipper::Poller)
26
38
  Flipper.unregister_groups
27
39
  Flipper.configuration = nil
28
40
  end
@@ -91,15 +103,3 @@ RSpec.shared_examples_for 'a DSL feature' do
91
103
  end.to raise_error(ArgumentError, /must be a String or Symbol/)
92
104
  end
93
105
  end
94
-
95
- RSpec.shared_examples_for 'a DSL boolean method' do
96
- it 'returns boolean with value set' do
97
- result = subject.send(method_name, true)
98
- expect(result).to be_instance_of(Flipper::Types::Boolean)
99
- expect(result.value).to be(true)
100
-
101
- result = subject.send(method_name, false)
102
- expect(result).to be_instance_of(Flipper::Types::Boolean)
103
- expect(result.value).to be(false)
104
- end
105
- end
@@ -0,0 +1 @@
1
+ actor_name_1: 'Actor #1'
@@ -0,0 +1,8 @@
1
+ if ENV["CI"] || ENV["FAIL_ON_OUTPUT"]
2
+ RSpec.configure do |config|
3
+ config.around do |example|
4
+ output = capture_output { example.run }
5
+ fail "Use `silence { }` to avoid printing to STDOUT/STDERR\n#{output}" unless output.empty?
6
+ end
7
+ end
8
+ end