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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +56 -10
- data/.github/workflows/examples.yml +54 -11
- data/.github/workflows/release.yml +54 -0
- data/.superset/config.json +4 -0
- data/CLAUDE.md +93 -0
- data/Changelog.md +1 -559
- data/Gemfile +17 -8
- data/README.md +34 -29
- data/Rakefile +2 -2
- data/benchmark/typecast_ips.rb +8 -0
- data/docs/images/banner.jpg +0 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/app.ru +12 -0
- data/examples/cloud/backoff_policy.rb +13 -0
- data/examples/cloud/basic.rb +22 -0
- data/examples/cloud/cloud_setup.rb +20 -0
- data/examples/cloud/forked.rb +36 -0
- data/examples/cloud/import.rb +17 -0
- data/examples/cloud/poll_interval/README.md +111 -0
- data/examples/cloud/poll_interval/client.rb +108 -0
- data/examples/cloud/poll_interval/server.rb +98 -0
- data/examples/cloud/threaded.rb +33 -0
- data/examples/dsl.rb +0 -14
- data/examples/expressions.rb +237 -0
- data/examples/strict.rb +18 -0
- data/exe/flipper +5 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +8 -4
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +27 -1
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/actor_limit.rb +54 -0
- data/lib/flipper/adapters/cache_base.rb +161 -0
- data/lib/flipper/adapters/dual_write.rb +7 -5
- data/lib/flipper/adapters/failover.rb +9 -7
- data/lib/flipper/adapters/failsafe.rb +2 -6
- data/lib/flipper/adapters/http/client.rb +53 -14
- data/lib/flipper/adapters/http/error.rb +2 -2
- data/lib/flipper/adapters/http.rb +56 -16
- data/lib/flipper/adapters/instrumented.rb +2 -6
- data/lib/flipper/adapters/memoizable.rb +17 -22
- data/lib/flipper/adapters/memory.rb +5 -7
- data/lib/flipper/adapters/operation_logger.rb +18 -92
- data/lib/flipper/adapters/poll.rb +16 -3
- data/lib/flipper/adapters/pstore.rb +18 -12
- data/lib/flipper/adapters/read_only.rb +8 -41
- data/lib/flipper/adapters/strict.rb +75 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
- data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
- data/lib/flipper/adapters/sync.rb +7 -7
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +314 -0
- data/lib/flipper/cloud/configuration.rb +267 -0
- data/lib/flipper/cloud/dsl.rb +27 -0
- data/lib/flipper/cloud/message_verifier.rb +95 -0
- data/lib/flipper/cloud/middleware.rb +63 -0
- data/lib/flipper/cloud/migrate.rb +71 -0
- data/lib/flipper/cloud/routes.rb +14 -0
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
- data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
- data/lib/flipper/cloud/telemetry/metric.rb +39 -0
- data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
- data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
- data/lib/flipper/cloud/telemetry.rb +191 -0
- data/lib/flipper/cloud.rb +54 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +48 -43
- data/lib/flipper/engine.rb +102 -0
- data/lib/flipper/export.rb +0 -2
- data/lib/flipper/exporters/json/export.rb +1 -1
- data/lib/flipper/exporters/json/v1.rb +1 -1
- data/lib/flipper/expression/builder.rb +73 -0
- data/lib/flipper/expression/constant.rb +25 -0
- data/lib/flipper/expression.rb +71 -0
- data/lib/flipper/expressions/all.rb +9 -0
- data/lib/flipper/expressions/any.rb +9 -0
- data/lib/flipper/expressions/boolean.rb +9 -0
- data/lib/flipper/expressions/comparable.rb +13 -0
- data/lib/flipper/expressions/equal.rb +9 -0
- data/lib/flipper/expressions/feature_enabled.rb +34 -0
- data/lib/flipper/expressions/greater_than.rb +9 -0
- data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/less_than.rb +9 -0
- data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/not_equal.rb +9 -0
- data/lib/flipper/expressions/now.rb +9 -0
- data/lib/flipper/expressions/number.rb +9 -0
- data/lib/flipper/expressions/percentage.rb +9 -0
- data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
- data/lib/flipper/expressions/property.rb +9 -0
- data/lib/flipper/expressions/random.rb +9 -0
- data/lib/flipper/expressions/string.rb +9 -0
- data/lib/flipper/expressions/time.rb +16 -0
- data/lib/flipper/feature.rb +63 -1
- data/lib/flipper/gate.rb +2 -1
- data/lib/flipper/gate_values.rb +5 -2
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/instrumentation/log_subscriber.rb +13 -5
- data/lib/flipper/instrumentation/statsd.rb +4 -2
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +0 -4
- data/lib/flipper/metadata.rb +8 -1
- data/lib/flipper/middleware/memoizer.rb +30 -14
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +54 -12
- data/lib/flipper/serializers/gzip.rb +22 -0
- data/lib/flipper/serializers/json.rb +17 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
- data/lib/flipper/test/shared_adapter_test.rb +77 -58
- data/lib/flipper/test_help.rb +43 -0
- data/lib/flipper/typecast.rb +37 -9
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +11 -1
- data/lib/flipper.rb +61 -8
- data/lib/generators/flipper/setup_generator.rb +68 -0
- data/lib/generators/flipper/templates/initializer.rb +45 -0
- data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
- data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
- data/lib/generators/flipper/update_generator.rb +35 -0
- data/package-lock.json +41 -0
- data/package.json +10 -0
- data/spec/fixtures/environment.rb +1 -0
- data/spec/flipper/adapter_builder_spec.rb +72 -0
- data/spec/flipper/adapter_spec.rb +21 -0
- data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
- data/spec/flipper/adapters/dual_write_spec.rb +15 -2
- data/spec/flipper/adapters/failover_spec.rb +12 -0
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +375 -74
- data/spec/flipper/adapters/instrumented_spec.rb +1 -1
- data/spec/flipper/adapters/memoizable_spec.rb +21 -21
- data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- data/spec/flipper/adapters/read_only_spec.rb +32 -17
- data/spec/flipper/adapters/strict_spec.rb +122 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -0
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
- data/spec/flipper/adapters/sync_spec.rb +13 -0
- data/spec/flipper/cli_spec.rb +217 -0
- data/spec/flipper/cloud/configuration_spec.rb +251 -0
- data/spec/flipper/cloud/dsl_spec.rb +82 -0
- data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
- data/spec/flipper/cloud/middleware_spec.rb +289 -0
- data/spec/flipper/cloud/migrate_spec.rb +160 -0
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
- data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
- data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
- data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
- data/spec/flipper/cloud/telemetry_spec.rb +208 -0
- data/spec/flipper/cloud_spec.rb +186 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +34 -73
- data/spec/flipper/engine_spec.rb +374 -0
- data/spec/flipper/exporters/json/v1_spec.rb +3 -3
- data/spec/flipper/expression/builder_spec.rb +248 -0
- data/spec/flipper/expression_spec.rb +188 -0
- data/spec/flipper/expressions/all_spec.rb +15 -0
- data/spec/flipper/expressions/any_spec.rb +15 -0
- data/spec/flipper/expressions/boolean_spec.rb +15 -0
- data/spec/flipper/expressions/equal_spec.rb +24 -0
- data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/greater_than_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_spec.rb +32 -0
- data/spec/flipper/expressions/not_equal_spec.rb +15 -0
- data/spec/flipper/expressions/now_spec.rb +11 -0
- data/spec/flipper/expressions/number_spec.rb +21 -0
- data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
- data/spec/flipper/expressions/percentage_spec.rb +15 -0
- data/spec/flipper/expressions/property_spec.rb +13 -0
- data/spec/flipper/expressions/random_spec.rb +9 -0
- data/spec/flipper/expressions/string_spec.rb +11 -0
- data/spec/flipper/expressions/time_spec.rb +29 -0
- data/spec/flipper/feature_spec.rb +380 -10
- data/spec/flipper/gate_values_spec.rb +2 -2
- data/spec/flipper/gates/expression_spec.rb +190 -0
- data/spec/flipper/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
- data/spec/flipper/middleware/memoizer_spec.rb +107 -7
- data/spec/flipper/model/active_record_spec.rb +72 -0
- data/spec/flipper/poller_spec.rb +347 -4
- data/spec/flipper/serializers/gzip_spec.rb +13 -0
- data/spec/flipper/serializers/json_spec.rb +13 -0
- data/spec/flipper/typecast_spec.rb +43 -7
- data/spec/flipper/types/actor_spec.rb +18 -1
- data/spec/flipper_integration_spec.rb +247 -16
- data/spec/flipper_spec.rb +92 -29
- data/spec/spec_helper.rb +24 -17
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/fail_on_output.rb +8 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/spec_helpers.rb +34 -8
- data/test/adapters/actor_limit_test.rb +20 -0
- data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
- data/test_rails/generators/flipper/update_generator_test.rb +96 -0
- data/test_rails/helper.rb +22 -2
- data/test_rails/system/test_help_test.rb +52 -0
- metadata +139 -75
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- 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) {
|
|
28
|
-
let(:five_percent_of_time) {
|
|
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?(
|
|
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?(
|
|
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?(
|
|
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?(
|
|
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(
|
|
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(
|
|
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?(
|
|
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?(
|
|
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?(
|
|
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?(
|
|
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) {
|
|
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
|
|
92
|
-
expect(described_class.
|
|
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
|
|
96
|
-
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
require 'rubygems'
|
|
8
|
-
require 'bundler'
|
|
5
|
+
require 'bundler/setup'
|
|
9
6
|
|
|
10
|
-
|
|
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'
|