flipper 0.28.3 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +56 -10
  4. data/.github/workflows/examples.yml +54 -11
  5. data/.github/workflows/release.yml +54 -0
  6. data/.superset/config.json +4 -0
  7. data/CLAUDE.md +93 -0
  8. data/Changelog.md +1 -559
  9. data/Gemfile +17 -8
  10. data/README.md +34 -29
  11. data/Rakefile +2 -2
  12. data/benchmark/typecast_ips.rb +8 -0
  13. data/docs/images/banner.jpg +0 -0
  14. data/docs/images/flipper_cloud.png +0 -0
  15. data/examples/cloud/app.ru +12 -0
  16. data/examples/cloud/backoff_policy.rb +13 -0
  17. data/examples/cloud/basic.rb +22 -0
  18. data/examples/cloud/cloud_setup.rb +20 -0
  19. data/examples/cloud/forked.rb +36 -0
  20. data/examples/cloud/import.rb +17 -0
  21. data/examples/cloud/poll_interval/README.md +111 -0
  22. data/examples/cloud/poll_interval/client.rb +108 -0
  23. data/examples/cloud/poll_interval/server.rb +98 -0
  24. data/examples/cloud/threaded.rb +33 -0
  25. data/examples/dsl.rb +0 -14
  26. data/examples/expressions.rb +237 -0
  27. data/examples/strict.rb +18 -0
  28. data/exe/flipper +5 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +8 -4
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +27 -1
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/actor_limit.rb +54 -0
  35. data/lib/flipper/adapters/cache_base.rb +161 -0
  36. data/lib/flipper/adapters/dual_write.rb +7 -5
  37. data/lib/flipper/adapters/failover.rb +9 -7
  38. data/lib/flipper/adapters/failsafe.rb +2 -6
  39. data/lib/flipper/adapters/http/client.rb +53 -14
  40. data/lib/flipper/adapters/http/error.rb +2 -2
  41. data/lib/flipper/adapters/http.rb +56 -16
  42. data/lib/flipper/adapters/instrumented.rb +2 -6
  43. data/lib/flipper/adapters/memoizable.rb +17 -22
  44. data/lib/flipper/adapters/memory.rb +5 -7
  45. data/lib/flipper/adapters/operation_logger.rb +18 -92
  46. data/lib/flipper/adapters/poll.rb +16 -3
  47. data/lib/flipper/adapters/pstore.rb +18 -12
  48. data/lib/flipper/adapters/read_only.rb +8 -41
  49. data/lib/flipper/adapters/strict.rb +75 -0
  50. data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
  51. data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
  52. data/lib/flipper/adapters/sync.rb +7 -7
  53. data/lib/flipper/adapters/wrapper.rb +54 -0
  54. data/lib/flipper/cli.rb +314 -0
  55. data/lib/flipper/cloud/configuration.rb +267 -0
  56. data/lib/flipper/cloud/dsl.rb +27 -0
  57. data/lib/flipper/cloud/message_verifier.rb +95 -0
  58. data/lib/flipper/cloud/middleware.rb +63 -0
  59. data/lib/flipper/cloud/migrate.rb +71 -0
  60. data/lib/flipper/cloud/routes.rb +14 -0
  61. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  62. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  63. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  64. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  65. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  66. data/lib/flipper/cloud/telemetry.rb +191 -0
  67. data/lib/flipper/cloud.rb +54 -0
  68. data/lib/flipper/configuration.rb +25 -4
  69. data/lib/flipper/dsl.rb +48 -43
  70. data/lib/flipper/engine.rb +102 -0
  71. data/lib/flipper/export.rb +0 -2
  72. data/lib/flipper/exporters/json/export.rb +1 -1
  73. data/lib/flipper/exporters/json/v1.rb +1 -1
  74. data/lib/flipper/expression/builder.rb +73 -0
  75. data/lib/flipper/expression/constant.rb +25 -0
  76. data/lib/flipper/expression.rb +71 -0
  77. data/lib/flipper/expressions/all.rb +9 -0
  78. data/lib/flipper/expressions/any.rb +9 -0
  79. data/lib/flipper/expressions/boolean.rb +9 -0
  80. data/lib/flipper/expressions/comparable.rb +13 -0
  81. data/lib/flipper/expressions/equal.rb +9 -0
  82. data/lib/flipper/expressions/feature_enabled.rb +34 -0
  83. data/lib/flipper/expressions/greater_than.rb +9 -0
  84. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  85. data/lib/flipper/expressions/less_than.rb +9 -0
  86. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  87. data/lib/flipper/expressions/not_equal.rb +9 -0
  88. data/lib/flipper/expressions/now.rb +9 -0
  89. data/lib/flipper/expressions/number.rb +9 -0
  90. data/lib/flipper/expressions/percentage.rb +9 -0
  91. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  92. data/lib/flipper/expressions/property.rb +9 -0
  93. data/lib/flipper/expressions/random.rb +9 -0
  94. data/lib/flipper/expressions/string.rb +9 -0
  95. data/lib/flipper/expressions/time.rb +16 -0
  96. data/lib/flipper/feature.rb +63 -1
  97. data/lib/flipper/gate.rb +2 -1
  98. data/lib/flipper/gate_values.rb +5 -2
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/instrumentation/log_subscriber.rb +13 -5
  101. data/lib/flipper/instrumentation/statsd.rb +4 -2
  102. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  103. data/lib/flipper/instrumentation/subscriber.rb +0 -4
  104. data/lib/flipper/metadata.rb +8 -1
  105. data/lib/flipper/middleware/memoizer.rb +30 -14
  106. data/lib/flipper/model/active_record.rb +23 -0
  107. data/lib/flipper/poller.rb +54 -12
  108. data/lib/flipper/serializers/gzip.rb +22 -0
  109. data/lib/flipper/serializers/json.rb +17 -0
  110. data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
  111. data/lib/flipper/test/shared_adapter_test.rb +77 -58
  112. data/lib/flipper/test_help.rb +43 -0
  113. data/lib/flipper/typecast.rb +37 -9
  114. data/lib/flipper/types/percentage.rb +1 -1
  115. data/lib/flipper/version.rb +11 -1
  116. data/lib/flipper.rb +61 -8
  117. data/lib/generators/flipper/setup_generator.rb +68 -0
  118. data/lib/generators/flipper/templates/initializer.rb +45 -0
  119. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  120. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  121. data/lib/generators/flipper/update_generator.rb +35 -0
  122. data/package-lock.json +41 -0
  123. data/package.json +10 -0
  124. data/spec/fixtures/environment.rb +1 -0
  125. data/spec/flipper/adapter_builder_spec.rb +72 -0
  126. data/spec/flipper/adapter_spec.rb +21 -0
  127. data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
  128. data/spec/flipper/adapters/dual_write_spec.rb +15 -2
  129. data/spec/flipper/adapters/failover_spec.rb +12 -0
  130. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  131. data/spec/flipper/adapters/http_spec.rb +375 -74
  132. data/spec/flipper/adapters/instrumented_spec.rb +1 -1
  133. data/spec/flipper/adapters/memoizable_spec.rb +21 -21
  134. data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
  135. data/spec/flipper/adapters/poll_spec.rb +41 -0
  136. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  137. data/spec/flipper/adapters/strict_spec.rb +122 -0
  138. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -0
  139. data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
  140. data/spec/flipper/adapters/sync_spec.rb +13 -0
  141. data/spec/flipper/cli_spec.rb +217 -0
  142. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  143. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  144. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  145. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  146. data/spec/flipper/cloud/migrate_spec.rb +160 -0
  147. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  148. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  149. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  150. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  151. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  152. data/spec/flipper/cloud_spec.rb +186 -0
  153. data/spec/flipper/configuration_spec.rb +17 -0
  154. data/spec/flipper/dsl_spec.rb +34 -73
  155. data/spec/flipper/engine_spec.rb +374 -0
  156. data/spec/flipper/exporters/json/v1_spec.rb +3 -3
  157. data/spec/flipper/expression/builder_spec.rb +248 -0
  158. data/spec/flipper/expression_spec.rb +188 -0
  159. data/spec/flipper/expressions/all_spec.rb +15 -0
  160. data/spec/flipper/expressions/any_spec.rb +15 -0
  161. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  162. data/spec/flipper/expressions/equal_spec.rb +24 -0
  163. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  164. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  165. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  166. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  167. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  168. data/spec/flipper/expressions/now_spec.rb +11 -0
  169. data/spec/flipper/expressions/number_spec.rb +21 -0
  170. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  171. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  172. data/spec/flipper/expressions/property_spec.rb +13 -0
  173. data/spec/flipper/expressions/random_spec.rb +9 -0
  174. data/spec/flipper/expressions/string_spec.rb +11 -0
  175. data/spec/flipper/expressions/time_spec.rb +29 -0
  176. data/spec/flipper/feature_spec.rb +380 -10
  177. data/spec/flipper/gate_values_spec.rb +2 -2
  178. data/spec/flipper/gates/expression_spec.rb +190 -0
  179. data/spec/flipper/identifier_spec.rb +4 -5
  180. data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
  181. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
  182. data/spec/flipper/middleware/memoizer_spec.rb +107 -7
  183. data/spec/flipper/model/active_record_spec.rb +72 -0
  184. data/spec/flipper/poller_spec.rb +347 -4
  185. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  186. data/spec/flipper/serializers/json_spec.rb +13 -0
  187. data/spec/flipper/typecast_spec.rb +43 -7
  188. data/spec/flipper/types/actor_spec.rb +18 -1
  189. data/spec/flipper_integration_spec.rb +247 -16
  190. data/spec/flipper_spec.rb +92 -29
  191. data/spec/spec_helper.rb +24 -17
  192. data/spec/support/actor_names.yml +1 -0
  193. data/spec/support/fail_on_output.rb +8 -0
  194. data/spec/support/fake_backoff_policy.rb +15 -0
  195. data/spec/support/spec_helpers.rb +34 -8
  196. data/test/adapters/actor_limit_test.rb +20 -0
  197. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  198. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  199. data/test_rails/helper.rb +22 -2
  200. data/test_rails/system/test_help_test.rb +52 -0
  201. metadata +139 -75
  202. data/.tool-versions +0 -1
  203. data/lib/flipper/railtie.rb +0 -47
  204. data/spec/flipper/railtie_spec.rb +0 -109
@@ -8,24 +8,31 @@ RSpec.describe Flipper do
8
8
  let(:dev_group) { flipper.group(:devs) }
9
9
 
10
10
  let(:admin_actor) do
11
- double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false
11
+ double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false, flipper_properties: {"admin" => true, "dev" => false}
12
12
  end
13
13
  let(:dev_actor) do
14
- double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true
14
+ double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true, flipper_properties: {"admin" => false, "dev" => true}
15
15
  end
16
16
 
17
17
  let(:admin_truthy_actor) do
18
- double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false
18
+ double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false, flipper_properties: {"admin" => "true-ish", "dev" => false}
19
19
  end
20
20
  let(:admin_falsey_actor) do
21
- double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false
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?)
@@ -69,11 +76,13 @@ RSpec.describe Flipper do
69
76
  end
70
77
 
71
78
  it 'enables feature for flipper actor in group' do
72
- expect(feature.enabled?(flipper.actor(admin_actor))).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_actor))).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
@@ -256,11 +265,13 @@ RSpec.describe Flipper do
256
265
  end
257
266
 
258
267
  it 'disables feature for flipper actor in group' do
259
- expect(feature.enabled?(flipper.actor(admin_actor))).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_actor))).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
@@ -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
@@ -373,12 +384,13 @@ RSpec.describe Flipper do
373
384
  end
374
385
 
375
386
  it 'returns true' do
376
- expect(feature.enabled?(flipper.actor(admin_actor))).to eq(true)
387
+ expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(true)
377
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_actor))).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)
382
394
  end
383
395
 
384
396
  it 'returns true if any actor is in enabled group' do
@@ -388,12 +400,13 @@ RSpec.describe Flipper do
388
400
 
389
401
  context 'for actor in disabled group' do
390
402
  it 'returns false' do
391
- expect(feature.enabled?(flipper.actor(dev_actor))).to eq(false)
403
+ expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(false)
392
404
  expect(feature.enabled?(dev_actor)).to eq(false)
393
405
  end
394
406
 
395
407
  it 'returns false for falsey block values' do
396
- expect(feature.enabled?(flipper.actor(admin_falsey_actor))).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)
397
410
  end
398
411
  end
399
412
 
@@ -549,4 +562,222 @@ RSpec.describe Flipper do
549
562
  expect(feature.enabled?(dev_actor)).to eq(false)
550
563
  end
551
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 FeatureEnabled" do
604
+ before do
605
+ @original_instance = Flipper.instance
606
+ Flipper.instance = flipper
607
+ end
608
+
609
+ after do
610
+ Flipper.instance = @original_instance
611
+ Thread.current[:flipper_evaluating_features] = nil
612
+ end
613
+
614
+ it "returns true when referenced feature is enabled" do
615
+ flipper.enable(:search_beta)
616
+ feature.enable Flipper.feature_enabled(:search_beta)
617
+
618
+ expect(feature.enabled?).to be(true)
619
+ end
620
+
621
+ it "returns false when referenced feature is disabled" do
622
+ feature.enable Flipper.feature_enabled(:search_beta)
623
+
624
+ expect(feature.enabled?).to be(false)
625
+ end
626
+
627
+ it "passes actor through to referenced feature" do
628
+ flipper.enable_actor(:search_beta, pitt)
629
+ feature.enable Flipper.feature_enabled(:search_beta)
630
+
631
+ expect(feature.enabled?(pitt)).to be(true)
632
+ expect(feature.enabled?(clooney)).to be(false)
633
+ end
634
+
635
+ it "returns false on circular dependency" do
636
+ feature.enable Flipper.feature_enabled(:other)
637
+ flipper[:other].enable Flipper.feature_enabled(:search)
638
+
639
+ expect(feature.enabled?).to be(false)
640
+ end
641
+
642
+ it "returns false on self-reference" do
643
+ feature.enable Flipper.feature_enabled(:search)
644
+
645
+ expect(feature.enabled?).to be(false)
646
+ end
647
+
648
+ it "cleans up thread-local state after evaluation" do
649
+ flipper.enable(:search_beta)
650
+ feature.enable Flipper.feature_enabled(:search_beta)
651
+
652
+ feature.enabled?
653
+ evaluating = Thread.current[:flipper_evaluating_features]
654
+ expect(evaluating.nil? || evaluating.empty?).to be(true)
655
+ end
656
+
657
+ it "cleans up thread-local state after self-reference" do
658
+ feature.enable Flipper.feature_enabled(:search)
659
+
660
+ feature.enabled?
661
+ evaluating = Thread.current[:flipper_evaluating_features]
662
+ expect(evaluating.nil? || evaluating.empty?).to be(true)
663
+ end
664
+
665
+ it "cleans up thread-local state after circular dependency" do
666
+ feature.enable Flipper.feature_enabled(:other)
667
+ flipper[:other].enable Flipper.feature_enabled(:search)
668
+
669
+ feature.enabled?
670
+ evaluating = Thread.current[:flipper_evaluating_features]
671
+ expect(evaluating.nil? || evaluating.empty?).to be(true)
672
+ end
673
+ end
674
+
675
+ context "for FeatureDisabled" do
676
+ before do
677
+ @original_instance = Flipper.instance
678
+ Flipper.instance = flipper
679
+ end
680
+
681
+ after do
682
+ Flipper.instance = @original_instance
683
+ Thread.current[:flipper_evaluating_features] = nil
684
+ end
685
+
686
+ it "returns true when referenced feature is disabled" do
687
+ feature.enable Flipper.feature_disabled(:old_checkout)
688
+
689
+ expect(feature.enabled?).to be(true)
690
+ end
691
+
692
+ it "returns false when referenced feature is enabled" do
693
+ flipper.enable(:old_checkout)
694
+ feature.enable Flipper.feature_disabled(:old_checkout)
695
+
696
+ expect(feature.enabled?).to be(false)
697
+ end
698
+ end
699
+
700
+ context "for FeatureEnabled composed with other expressions" do
701
+ before do
702
+ @original_instance = Flipper.instance
703
+ Flipper.instance = flipper
704
+ end
705
+
706
+ after do
707
+ Flipper.instance = @original_instance
708
+ Thread.current[:flipper_evaluating_features] = nil
709
+ end
710
+
711
+ it "works with Any" do
712
+ feature.enable Flipper.any(
713
+ Flipper.feature_enabled(:beta_program),
714
+ Flipper.property(:plan).eq("basic")
715
+ )
716
+
717
+ expect(feature.enabled?(basic_plan_actor)).to be(true)
718
+ expect(feature.enabled?(premium_plan_actor)).to be(false)
719
+
720
+ flipper.enable(:beta_program)
721
+ expect(feature.enabled?(premium_plan_actor)).to be(true)
722
+ end
723
+
724
+ it "works with All" do
725
+ flipper.enable(:basic_search)
726
+ feature.enable Flipper.all(
727
+ Flipper.feature_enabled(:basic_search),
728
+ Flipper.property(:plan).eq("basic")
729
+ )
730
+
731
+ expect(feature.enabled?(basic_plan_actor)).to be(true)
732
+ expect(feature.enabled?(premium_plan_actor)).to be(false)
733
+ end
734
+ end
735
+
736
+ context "for All" do
737
+ it "works" do
738
+ true_actor = Flipper::Actor.new("User;1", {
739
+ "plan" => "basic",
740
+ "age" => 21,
741
+ })
742
+ false_actor = Flipper::Actor.new("User;1", {
743
+ "plan" => "basic",
744
+ "age" => 20,
745
+ })
746
+ expression = Flipper.all(
747
+ Flipper.property(:plan).eq("basic"),
748
+ Flipper.property(:age).eq(21)
749
+ )
750
+ feature.enable expression
751
+
752
+ expect(feature.enabled?(true_actor)).to be(true)
753
+ expect(feature.enabled?(false_actor)).to be(false)
754
+ end
755
+
756
+ it "works when nested" do
757
+ admin_actor = Flipper::Actor.new("User;1", {
758
+ "admin" => true,
759
+ })
760
+ true_actor = Flipper::Actor.new("User;1", {
761
+ "plan" => "basic",
762
+ "age" => 21,
763
+ })
764
+ false_actor = Flipper::Actor.new("User;1", {
765
+ "plan" => "basic",
766
+ "age" => 20,
767
+ })
768
+ expression = Flipper.any(
769
+ Flipper.property(:admin).eq(true),
770
+ Flipper.all(
771
+ Flipper.property(:plan).eq("basic"),
772
+ Flipper.property(:age).eq(21)
773
+ )
774
+ )
775
+
776
+ feature.enable expression
777
+
778
+ expect(feature.enabled?(admin_actor)).to be(true)
779
+ expect(feature.enabled?(true_actor)).to be(true)
780
+ expect(feature.enabled?(false_actor)).to be(false)
781
+ end
782
+ end
552
783
  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)
@@ -212,6 +218,11 @@ RSpec.describe Flipper do
212
218
  expect(described_class.adapter).to eq(described_class.instance.adapter)
213
219
  end
214
220
 
221
+ it 'delegates adapter_stack to instance' do
222
+ expect(described_class.adapter_stack).to eq(described_class.instance.adapter_stack)
223
+ expect(described_class.adapter_stack).to eq("memoizable -> memory")
224
+ end
225
+
215
226
  it 'delegates memoize= to instance' do
216
227
  expect(described_class.adapter.memoizing?).to be(false)
217
228
  described_class.memoize = true
@@ -222,6 +233,10 @@ RSpec.describe Flipper do
222
233
  expect(described_class.memoizing?).to eq(described_class.adapter.memoizing?)
223
234
  end
224
235
 
236
+ it 'delegates read_only? to instance' do
237
+ expect(described_class.read_only?).to eq(described_class.adapter.read_only?)
238
+ end
239
+
225
240
  it 'delegates sync stuff to instance and does nothing' do
226
241
  expect(described_class.sync).to be(nil)
227
242
  expect(described_class.sync_secret).to be(nil)
@@ -231,7 +246,7 @@ RSpec.describe Flipper do
231
246
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
232
247
  with({
233
248
  headers: {
234
- 'Flipper-Cloud-Token'=>'asdf',
249
+ 'flipper-cloud-token'=>'asdf',
235
250
  },
236
251
  }).to_return(status: 200, body: '{"features": {}}', headers: {})
237
252
  cloud_configuration = Flipper::Cloud::Configuration.new({
@@ -303,7 +318,7 @@ RSpec.describe Flipper do
303
318
 
304
319
  describe '.group_exists' do
305
320
  it 'returns true if the group is already created' do
306
- group = described_class.register('admins', &:admin?)
321
+ described_class.register('admins', &:admin?)
307
322
  expect(described_class.group_exists?(:admins)).to eq(true)
308
323
  end
309
324
 
@@ -356,4 +371,52 @@ RSpec.describe Flipper do
356
371
  expect(described_class.instance_variable_get('@groups_registry')).to eq(registry)
357
372
  end
358
373
  end
374
+
375
+ describe ".constant" do
376
+ it "returns Flipper::Expression::Constant instance" do
377
+ expect(described_class.constant(false)).to eq(Flipper::Expression::Constant.new(false))
378
+ expect(described_class.constant("string")).to eq(Flipper::Expression::Constant.new("string"))
379
+ end
380
+ end
381
+
382
+ describe ".property" do
383
+ it "returns Flipper::Expressions::Property expression" do
384
+ expect(Flipper.property("name")).to eq(Flipper::Expression.build(Property: "name"))
385
+ end
386
+ end
387
+
388
+ describe ".boolean" do
389
+ it "returns Flipper::Expressions::Boolean expression" do
390
+ expect(described_class.boolean(true)).to eq(Flipper::Expression.build(Boolean: true))
391
+ expect(described_class.boolean(false)).to eq(Flipper::Expression.build(Boolean: false))
392
+ end
393
+ end
394
+
395
+ describe ".random" do
396
+ it "returns Flipper::Expressions::Random expression" do
397
+ expect(Flipper.random(100)).to eq(Flipper::Expression.build(Random: 100))
398
+ end
399
+ end
400
+
401
+ describe ".any" do
402
+ let(:age_expression) { Flipper.property(:age).gte(21) }
403
+ let(:plan_expression) { Flipper.property(:plan).eq("basic") }
404
+
405
+ it "returns Flipper::Expressions::Any instance" do
406
+ expect(Flipper.any(age_expression, plan_expression)).to eq(
407
+ Flipper::Expression.build({Any: [age_expression, plan_expression]})
408
+ )
409
+ end
410
+ end
411
+
412
+ describe ".all" do
413
+ let(:age_expression) { Flipper.property(:age).gte(21) }
414
+ let(:plan_expression) { Flipper.property(:plan).eq("basic") }
415
+
416
+ it "returns Flipper::Expressions::All instance" do
417
+ expect(Flipper.all(age_expression, plan_expression)).to eq(
418
+ Flipper::Expression.build({All: [age_expression, plan_expression]})
419
+ )
420
+ end
421
+ end
359
422
  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,16 +20,32 @@ 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
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)
25
37
  Flipper::Poller.reset if defined?(Flipper::Poller)
26
38
  Flipper.unregister_groups
27
39
  Flipper.configuration = nil
28
40
  end
29
41
 
42
+ config.after(:example) do
43
+ # Stop any pollers started during the test BEFORE WebMock clears stubs
44
+ # in its own after hook (RSpec runs after hooks in reverse registration
45
+ # order, so ours runs first since webmock/rspec was required before this).
46
+ Flipper::Poller.reset if defined?(Flipper::Poller)
47
+ end
48
+
30
49
  config.disable_monkey_patching!
31
50
 
32
51
  config.filter_run focus: true
@@ -91,15 +110,3 @@ RSpec.shared_examples_for 'a DSL feature' do
91
110
  end.to raise_error(ArgumentError, /must be a String or Symbol/)
92
111
  end
93
112
  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
@@ -0,0 +1,15 @@
1
+ class FakeBackoffPolicy
2
+ def initialize
3
+ @retries = 0
4
+ end
5
+
6
+ attr_reader :retries
7
+
8
+ def next_interval
9
+ @retries += 1
10
+ 0
11
+ end
12
+
13
+ def reset
14
+ end
15
+ end