flipper 1.0.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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +50 -7
- data/.github/workflows/examples.yml +50 -8
- data/CLAUDE.md +74 -0
- data/Changelog.md +1 -584
- data/Gemfile +15 -8
- data/README.md +31 -27
- 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/backoff_policy.rb +13 -0
- data/examples/cloud/cloud_setup.rb +16 -0
- data/examples/cloud/forked.rb +7 -2
- data/examples/cloud/threaded.rb +15 -18
- data/examples/expressions.rb +213 -0
- data/examples/strict.rb +18 -0
- data/exe/flipper +5 -0
- data/flipper.gemspec +6 -3
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +10 -0
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/actor_limit.rb +28 -0
- data/lib/flipper/adapters/cache_base.rb +143 -0
- data/lib/flipper/adapters/dual_write.rb +1 -3
- data/lib/flipper/adapters/failover.rb +0 -4
- data/lib/flipper/adapters/failsafe.rb +0 -4
- data/lib/flipper/adapters/http/client.rb +40 -12
- data/lib/flipper/adapters/http/error.rb +2 -2
- data/lib/flipper/adapters/http.rb +19 -14
- data/lib/flipper/adapters/instrumented.rb +0 -4
- data/lib/flipper/adapters/memoizable.rb +14 -19
- data/lib/flipper/adapters/memory.rb +4 -6
- data/lib/flipper/adapters/operation_logger.rb +18 -92
- data/lib/flipper/adapters/poll.rb +16 -3
- data/lib/flipper/adapters/pstore.rb +17 -11
- data/lib/flipper/adapters/read_only.rb +8 -41
- data/lib/flipper/adapters/strict.rb +45 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
- data/lib/flipper/adapters/sync.rb +0 -4
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +263 -0
- data/lib/flipper/cloud/configuration.rb +131 -54
- data/lib/flipper/cloud/middleware.rb +5 -5
- 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 +1 -1
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +51 -0
- data/lib/flipper/engine.rb +42 -3
- 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/duration.rb +28 -0
- data/lib/flipper/expressions/equal.rb +9 -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 +9 -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 +4 -1
- data/lib/flipper/middleware/memoizer.rb +29 -13
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +9 -8
- data/lib/flipper/serializers/gzip.rb +22 -0
- data/lib/flipper/serializers/json.rb +17 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +46 -27
- data/lib/flipper/test/shared_adapter_test.rb +41 -22
- 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 +41 -2
- 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 +1 -0
- data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +135 -74
- data/spec/flipper/adapters/memoizable_spec.rb +15 -15
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- data/spec/flipper/adapters/read_only_spec.rb +26 -11
- data/spec/flipper/adapters/strict_spec.rb +64 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
- data/spec/flipper/cli_spec.rb +166 -0
- data/spec/flipper/cloud/configuration_spec.rb +39 -57
- data/spec/flipper/cloud/dsl_spec.rb +6 -6
- data/spec/flipper/cloud/middleware_spec.rb +8 -8
- 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 +31 -25
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +39 -3
- data/spec/flipper/engine_spec.rb +226 -42
- 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/duration_spec.rb +43 -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 +13 -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 +108 -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 +79 -10
- data/spec/flipper/model/active_record_spec.rb +72 -0
- 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 +102 -4
- data/spec/flipper_spec.rb +91 -3
- data/spec/spec_helper.rb +17 -5
- 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 +145 -29
- data/lib/flipper/cloud/instrumenter.rb +0 -48
- data/spec/support/climate_control.rb +0 -7
|
@@ -76,6 +76,26 @@ RSpec.describe Flipper::Feature do
|
|
|
76
76
|
expect(subject.enabled?(actors)).to be(false)
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
|
+
|
|
80
|
+
context "for an object that implements .nil? == true" do
|
|
81
|
+
let(:actor) { Flipper::Actor.new("User;1") }
|
|
82
|
+
|
|
83
|
+
before do
|
|
84
|
+
def actor.nil?
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'returns true if feature is enabled' do
|
|
90
|
+
subject.enable
|
|
91
|
+
expect(subject.enabled?(actor)).to be(true)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'returns false if feature is disabled' do
|
|
95
|
+
subject.disable
|
|
96
|
+
expect(subject.enabled?(actor)).to be(false)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
79
99
|
end
|
|
80
100
|
|
|
81
101
|
describe '#to_s' do
|
|
@@ -109,7 +129,7 @@ RSpec.describe Flipper::Feature do
|
|
|
109
129
|
instance.gates.each do |gate|
|
|
110
130
|
expect(gate).to be_a(Flipper::Gate)
|
|
111
131
|
end
|
|
112
|
-
expect(instance.gates.size).to be(
|
|
132
|
+
expect(instance.gates.size).to be(6)
|
|
113
133
|
end
|
|
114
134
|
end
|
|
115
135
|
|
|
@@ -195,7 +215,7 @@ RSpec.describe Flipper::Feature do
|
|
|
195
215
|
|
|
196
216
|
it 'is recorded for enable' do
|
|
197
217
|
actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
|
|
198
|
-
|
|
218
|
+
subject.gate_for(actor)
|
|
199
219
|
|
|
200
220
|
subject.enable(actor)
|
|
201
221
|
|
|
@@ -210,7 +230,7 @@ RSpec.describe Flipper::Feature do
|
|
|
210
230
|
|
|
211
231
|
it 'always instruments flipper type instance for enable' do
|
|
212
232
|
actor = Flipper::Actor.new('1')
|
|
213
|
-
|
|
233
|
+
subject.gate_for(actor)
|
|
214
234
|
|
|
215
235
|
subject.enable(actor)
|
|
216
236
|
|
|
@@ -221,7 +241,6 @@ RSpec.describe Flipper::Feature do
|
|
|
221
241
|
|
|
222
242
|
it 'is recorded for disable' do
|
|
223
243
|
thing = Flipper::Types::Boolean.new
|
|
224
|
-
gate = subject.gate_for(thing)
|
|
225
244
|
|
|
226
245
|
subject.disable(thing)
|
|
227
246
|
|
|
@@ -266,7 +285,6 @@ RSpec.describe Flipper::Feature do
|
|
|
266
285
|
|
|
267
286
|
it 'always instruments flipper type instance for disable' do
|
|
268
287
|
actor = Flipper::Actor.new('1')
|
|
269
|
-
gate = subject.gate_for(actor)
|
|
270
288
|
|
|
271
289
|
subject.disable(actor)
|
|
272
290
|
|
|
@@ -693,6 +711,361 @@ RSpec.describe Flipper::Feature do
|
|
|
693
711
|
end
|
|
694
712
|
end
|
|
695
713
|
|
|
714
|
+
describe '#expression' do
|
|
715
|
+
it "returns nil if feature has no expression" do
|
|
716
|
+
expect(subject.expression).to be(nil)
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
it "returns expression if feature has expression" do
|
|
720
|
+
expression = Flipper.property(:plan).eq("basic")
|
|
721
|
+
subject.enable_expression expression
|
|
722
|
+
expect(subject.expression).to eq(expression)
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
describe '#enable_expression/disable_expression' do
|
|
727
|
+
context "with expression instance" do
|
|
728
|
+
it "updates gate values to equal expression or clears expression" do
|
|
729
|
+
expression = Flipper.property(:plan).eq("basic")
|
|
730
|
+
expect(subject.gate_values.expression).to be(nil)
|
|
731
|
+
subject.enable_expression(expression)
|
|
732
|
+
expect(subject.gate_values.expression).to eq(expression.value)
|
|
733
|
+
subject.disable_expression
|
|
734
|
+
expect(subject.gate_values.expression).to be(nil)
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
context "with Hash" do
|
|
739
|
+
it "updates gate values to equal expression or clears expression" do
|
|
740
|
+
expression = Flipper.property(:plan).eq("basic")
|
|
741
|
+
expect(subject.gate_values.expression).to be(nil)
|
|
742
|
+
subject.enable_expression(expression.value)
|
|
743
|
+
expect(subject.gate_values.expression).to eq(expression.value)
|
|
744
|
+
subject.disable_expression
|
|
745
|
+
expect(subject.gate_values.expression).to be(nil)
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
describe "#add_expression" do
|
|
751
|
+
context "when nothing enabled" do
|
|
752
|
+
context "with Expression instance" do
|
|
753
|
+
it "sets expression to Expression" do
|
|
754
|
+
expression = Flipper.property(:plan).eq("basic")
|
|
755
|
+
subject.add_expression(expression)
|
|
756
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
757
|
+
expect(subject.expression).to eq(expression)
|
|
758
|
+
end
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
context "with Any instance" do
|
|
762
|
+
it "sets expression to Any" do
|
|
763
|
+
expression = Flipper.any(Flipper.property(:plan).eq("basic"))
|
|
764
|
+
subject.add_expression(expression)
|
|
765
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
766
|
+
expect(subject.expression).to eq(expression)
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
context "with All instance" do
|
|
771
|
+
it "sets expression to All" do
|
|
772
|
+
expression = Flipper.all(Flipper.property(:plan).eq("basic"))
|
|
773
|
+
subject.add_expression(expression)
|
|
774
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
775
|
+
expect(subject.expression).to eq(expression)
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
context "when Expression enabled" do
|
|
781
|
+
let(:expression) { Flipper.property(:plan).eq("basic") }
|
|
782
|
+
|
|
783
|
+
before do
|
|
784
|
+
subject.enable_expression expression
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
context "with Expression instance" do
|
|
788
|
+
it "changes expression to Any and adds new Expression" do
|
|
789
|
+
new_expression = Flipper.property(:age).gte(21)
|
|
790
|
+
subject.add_expression(new_expression)
|
|
791
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
792
|
+
expect(subject.expression.args).to include(expression)
|
|
793
|
+
expect(subject.expression.args).to include(new_expression)
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
context "with Any instance" do
|
|
798
|
+
it "changes expression to Any and adds new Any" do
|
|
799
|
+
new_expression = Flipper.any(Flipper.property(:age).eq(21))
|
|
800
|
+
subject.add_expression new_expression
|
|
801
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
802
|
+
expect(subject.expression.args).to include(expression)
|
|
803
|
+
expect(subject.expression.args).to include(new_expression)
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
context "with All instance" do
|
|
808
|
+
it "changes expression to Any and adds new All" do
|
|
809
|
+
new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
|
|
810
|
+
subject.add_expression new_expression
|
|
811
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
812
|
+
expect(subject.expression.args).to include(expression)
|
|
813
|
+
expect(subject.expression.args).to include(new_expression)
|
|
814
|
+
end
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
context "when Any enabled" do
|
|
819
|
+
let(:condition) { Flipper.property(:plan).eq("basic") }
|
|
820
|
+
let(:expression) { Flipper.any(condition) }
|
|
821
|
+
|
|
822
|
+
before do
|
|
823
|
+
subject.enable_expression expression
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
context "with Expression instance" do
|
|
827
|
+
it "adds Expression to Any" do
|
|
828
|
+
new_expression = Flipper.property(:age).gte(21)
|
|
829
|
+
subject.add_expression(new_expression)
|
|
830
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
831
|
+
expect(subject.expression.args).to include(condition)
|
|
832
|
+
expect(subject.expression.args).to include(new_expression)
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
context "with Any instance" do
|
|
837
|
+
it "adds Any to Any" do
|
|
838
|
+
new_expression = Flipper.any(Flipper.property(:age).gte(21))
|
|
839
|
+
subject.add_expression(new_expression)
|
|
840
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
841
|
+
expect(subject.expression.args).to include(condition)
|
|
842
|
+
expect(subject.expression.args).to include(new_expression)
|
|
843
|
+
end
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
context "with All instance" do
|
|
847
|
+
it "adds All to Any" do
|
|
848
|
+
new_expression = Flipper.all(Flipper.property(:age).gte(21))
|
|
849
|
+
subject.add_expression(new_expression)
|
|
850
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
851
|
+
expect(subject.expression.args).to include(condition)
|
|
852
|
+
expect(subject.expression.args).to include(new_expression)
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
context "when All enabled" do
|
|
858
|
+
let(:condition) { Flipper.property(:plan).eq("basic") }
|
|
859
|
+
let(:expression) { Flipper.all(condition) }
|
|
860
|
+
|
|
861
|
+
before do
|
|
862
|
+
subject.enable_expression expression
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
context "with Expression instance" do
|
|
866
|
+
it "adds Expression to All" do
|
|
867
|
+
new_expression = Flipper.property(:age).gte(21)
|
|
868
|
+
subject.add_expression(new_expression)
|
|
869
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
870
|
+
expect(subject.expression.args).to include(condition)
|
|
871
|
+
expect(subject.expression.args).to include(new_expression)
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
context "with Any instance" do
|
|
876
|
+
it "adds Any to All" do
|
|
877
|
+
new_expression = Flipper.any(Flipper.property(:age).gte(21))
|
|
878
|
+
subject.add_expression(new_expression)
|
|
879
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
880
|
+
expect(subject.expression.args).to include(condition)
|
|
881
|
+
expect(subject.expression.args).to include(new_expression)
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
context "with All instance" do
|
|
886
|
+
it "adds All to All" do
|
|
887
|
+
new_expression = Flipper.all(Flipper.property(:age).gte(21))
|
|
888
|
+
subject.add_expression(new_expression)
|
|
889
|
+
expect(subject.expression).to be_instance_of(Flipper::Expression)
|
|
890
|
+
expect(subject.expression.args).to include(condition)
|
|
891
|
+
expect(subject.expression.args).to include(new_expression)
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
describe '#remove_expression' do
|
|
898
|
+
context "when nothing enabled" do
|
|
899
|
+
context "with Expression instance" do
|
|
900
|
+
it "does nothing" do
|
|
901
|
+
expression = Flipper.property(:plan).eq("basic")
|
|
902
|
+
subject.remove_expression(expression)
|
|
903
|
+
expect(subject.expression).to be(nil)
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
context "with Any instance" do
|
|
908
|
+
it "does nothing" do
|
|
909
|
+
expression = Flipper.any(Flipper.property(:plan).eq("basic"))
|
|
910
|
+
subject.remove_expression expression
|
|
911
|
+
expect(subject.expression).to be(nil)
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
context "with All instance" do
|
|
916
|
+
it "does nothing" do
|
|
917
|
+
expression = Flipper.all(Flipper.property(:plan).eq("basic"))
|
|
918
|
+
subject.remove_expression expression
|
|
919
|
+
expect(subject.expression).to be(nil)
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
context "when Expression enabled" do
|
|
925
|
+
let(:expression) { Flipper.property(:plan).eq("basic") }
|
|
926
|
+
|
|
927
|
+
before do
|
|
928
|
+
subject.enable_expression expression
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
context "with Expression instance" do
|
|
932
|
+
it "changes expression to Any and removes Expression if it matches" do
|
|
933
|
+
new_expression = Flipper.property(:plan).eq("basic")
|
|
934
|
+
subject.remove_expression new_expression
|
|
935
|
+
expect(subject.expression).to eq(Flipper.any)
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
it "changes expression to Any if Expression doesn't match" do
|
|
939
|
+
new_expression = Flipper.property(:plan).eq("premium")
|
|
940
|
+
subject.remove_expression new_expression
|
|
941
|
+
expect(subject.expression).to eq(Flipper.any(expression))
|
|
942
|
+
end
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
context "with Any instance" do
|
|
946
|
+
it "changes expression to Any and does nothing" do
|
|
947
|
+
new_expression = Flipper.any(Flipper.property(:plan).eq("basic"))
|
|
948
|
+
subject.remove_expression new_expression
|
|
949
|
+
expect(subject.expression).to eq(Flipper.any(expression))
|
|
950
|
+
end
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
context "with All instance" do
|
|
954
|
+
it "changes expression to Any and does nothing" do
|
|
955
|
+
new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
|
|
956
|
+
subject.remove_expression new_expression
|
|
957
|
+
expect(subject.expression).to eq(Flipper.any(expression))
|
|
958
|
+
end
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
context "when Any enabled" do
|
|
963
|
+
let(:condition) { Flipper.property(:plan).eq("basic") }
|
|
964
|
+
let(:expression) { Flipper.any condition }
|
|
965
|
+
|
|
966
|
+
before do
|
|
967
|
+
subject.enable_expression expression
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
context "with Expression instance" do
|
|
971
|
+
it "removes Expression if it matches" do
|
|
972
|
+
subject.remove_expression condition
|
|
973
|
+
expect(subject.expression).to eq(Flipper.any)
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
it "does nothing if Expression does not match" do
|
|
977
|
+
subject.remove_expression Flipper.property(:plan).eq("premium")
|
|
978
|
+
expect(subject.expression).to eq(expression)
|
|
979
|
+
end
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
context "with Any instance" do
|
|
983
|
+
it "removes Any if it matches" do
|
|
984
|
+
new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
|
|
985
|
+
subject.add_expression new_expression
|
|
986
|
+
expect(subject.expression.args.size).to be(2)
|
|
987
|
+
subject.remove_expression new_expression
|
|
988
|
+
expect(subject.expression).to eq(expression)
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
it "does nothing if Any does not match" do
|
|
992
|
+
new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
|
|
993
|
+
subject.remove_expression new_expression
|
|
994
|
+
expect(subject.expression).to eq(expression)
|
|
995
|
+
end
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
context "with All instance" do
|
|
999
|
+
it "removes All if it matches" do
|
|
1000
|
+
new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
|
|
1001
|
+
subject.add_expression new_expression
|
|
1002
|
+
expect(subject.expression.args.size).to be(2)
|
|
1003
|
+
subject.remove_expression new_expression
|
|
1004
|
+
expect(subject.expression).to eq(expression)
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
it "does nothing if All does not match" do
|
|
1008
|
+
new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
|
|
1009
|
+
subject.remove_expression new_expression
|
|
1010
|
+
expect(subject.expression).to eq(expression)
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
context "when All enabled" do
|
|
1016
|
+
let(:condition) { Flipper.property(:plan).eq("basic") }
|
|
1017
|
+
let(:expression) { Flipper.all condition }
|
|
1018
|
+
|
|
1019
|
+
before do
|
|
1020
|
+
subject.enable_expression expression
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
context "with Expression instance" do
|
|
1024
|
+
it "removes Expression if it matches" do
|
|
1025
|
+
subject.remove_expression condition
|
|
1026
|
+
expect(subject.expression).to eq(Flipper.all)
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
it "does nothing if Expression does not match" do
|
|
1030
|
+
subject.remove_expression Flipper.property(:plan).eq("premium")
|
|
1031
|
+
expect(subject.expression).to eq(expression)
|
|
1032
|
+
end
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
context "with Any instance" do
|
|
1036
|
+
it "removes Any if it matches" do
|
|
1037
|
+
new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
|
|
1038
|
+
subject.add_expression new_expression
|
|
1039
|
+
expect(subject.expression.args.size).to be(2)
|
|
1040
|
+
subject.remove_expression new_expression
|
|
1041
|
+
expect(subject.expression).to eq(expression)
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
it "does nothing if Any does not match" do
|
|
1045
|
+
new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
|
|
1046
|
+
subject.remove_expression new_expression
|
|
1047
|
+
expect(subject.expression).to eq(expression)
|
|
1048
|
+
end
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
context "with All instance" do
|
|
1052
|
+
it "removes All if it matches" do
|
|
1053
|
+
new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
|
|
1054
|
+
subject.add_expression new_expression
|
|
1055
|
+
expect(subject.expression.args.size).to be(2)
|
|
1056
|
+
subject.remove_expression new_expression
|
|
1057
|
+
expect(subject.expression).to eq(expression)
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
it "does nothing if All does not match" do
|
|
1061
|
+
new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
|
|
1062
|
+
subject.remove_expression new_expression
|
|
1063
|
+
expect(subject.expression).to eq(expression)
|
|
1064
|
+
end
|
|
1065
|
+
end
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1068
|
+
|
|
696
1069
|
describe '#enable_actor/disable_actor' do
|
|
697
1070
|
context 'with object that responds to flipper_id' do
|
|
698
1071
|
it 'updates the gate values to include the actor' do
|
|
@@ -721,8 +1094,6 @@ RSpec.describe Flipper::Feature do
|
|
|
721
1094
|
describe '#enable_group/disable_group' do
|
|
722
1095
|
context 'with symbol group name' do
|
|
723
1096
|
it 'updates the gate values to include the group' do
|
|
724
|
-
actor = Flipper::Actor.new(5)
|
|
725
|
-
group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
|
|
726
1097
|
expect(subject.gate_values.groups).to be_empty
|
|
727
1098
|
subject.enable_group(:five_only)
|
|
728
1099
|
expect(subject.gate_values.groups).to eq(Set['five_only'])
|
|
@@ -733,8 +1104,6 @@ RSpec.describe Flipper::Feature do
|
|
|
733
1104
|
|
|
734
1105
|
context 'with string group name' do
|
|
735
1106
|
it 'updates the gate values to include the group' do
|
|
736
|
-
actor = Flipper::Actor.new(5)
|
|
737
|
-
group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
|
|
738
1107
|
expect(subject.gate_values.groups).to be_empty
|
|
739
1108
|
subject.enable_group('five_only')
|
|
740
1109
|
expect(subject.gate_values.groups).to eq(Set['five_only'])
|
|
@@ -745,7 +1114,6 @@ RSpec.describe Flipper::Feature do
|
|
|
745
1114
|
|
|
746
1115
|
context 'with group instance' do
|
|
747
1116
|
it 'updates the gate values for the group' do
|
|
748
|
-
actor = Flipper::Actor.new(5)
|
|
749
1117
|
group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
|
|
750
1118
|
expect(subject.gate_values.groups).to be_empty
|
|
751
1119
|
subject.enable_group(group)
|
|
@@ -845,12 +1213,14 @@ RSpec.describe Flipper::Feature do
|
|
|
845
1213
|
:actor,
|
|
846
1214
|
:boolean,
|
|
847
1215
|
:group,
|
|
1216
|
+
:expression,
|
|
848
1217
|
])
|
|
849
1218
|
|
|
850
1219
|
expect(subject.disabled_gate_names.to_set).to eq(Set[
|
|
851
1220
|
:actor,
|
|
852
1221
|
:boolean,
|
|
853
1222
|
:group,
|
|
1223
|
+
:expression,
|
|
854
1224
|
])
|
|
855
1225
|
end
|
|
856
1226
|
end
|
|
@@ -80,13 +80,13 @@ RSpec.describe Flipper::GateValues do
|
|
|
80
80
|
it 'raises argument error for percentage of time value that cannot be converted to an integer' do
|
|
81
81
|
expect do
|
|
82
82
|
described_class.new(percentage_of_time: ['asdf'])
|
|
83
|
-
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a
|
|
83
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a number))
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
it 'raises argument error for percentage of actors value that cannot be converted to an int' do
|
|
87
87
|
expect do
|
|
88
88
|
described_class.new(percentage_of_actors: ['asdf'])
|
|
89
|
-
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a
|
|
89
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a number))
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
it 'raises argument error for actors value that cannot be converted to a set' do
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
RSpec.describe Flipper::Gates::Expression do
|
|
2
|
+
let(:feature_name) { :search }
|
|
3
|
+
|
|
4
|
+
subject do
|
|
5
|
+
described_class.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def context(expression, properties: {})
|
|
9
|
+
Flipper::FeatureCheckContext.new(
|
|
10
|
+
feature_name: feature_name,
|
|
11
|
+
values: Flipper::GateValues.new(expression: expression),
|
|
12
|
+
actors: [Flipper::Types::Actor.new(Flipper::Actor.new(1, properties))]
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#enabled?' do
|
|
17
|
+
context 'for nil value' do
|
|
18
|
+
it 'returns false' do
|
|
19
|
+
expect(subject.enabled?(nil)).to eq(false)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context 'for empty value' do
|
|
24
|
+
it 'returns false' do
|
|
25
|
+
expect(subject.enabled?({})).to eq(false)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "for not empty value" do
|
|
30
|
+
it 'returns true' do
|
|
31
|
+
expect(subject.enabled?({"Boolean" => [true]})).to eq(true)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#open?' do
|
|
37
|
+
context 'for expression that evaluates to true' do
|
|
38
|
+
it 'returns true' do
|
|
39
|
+
expression = Flipper.boolean(true).eq(true)
|
|
40
|
+
expect(subject.open?(context(expression.value))).to be(true)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context 'for expression that evaluates to false' do
|
|
45
|
+
it 'returns false' do
|
|
46
|
+
expression = Flipper.boolean(true).eq(false)
|
|
47
|
+
expect(subject.open?(context(expression.value))).to be(false)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'for properties that have string keys' do
|
|
52
|
+
it 'returns true when expression evalutes to true' do
|
|
53
|
+
expression = Flipper.property(:type).eq("User")
|
|
54
|
+
context = context(expression.value, properties: {"type" => "User"})
|
|
55
|
+
expect(subject.open?(context)).to be(true)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'returns false when expression evaluates to false' do
|
|
59
|
+
expression = Flipper.property(:type).eq("User")
|
|
60
|
+
context = context(expression.value, properties: {"type" => "Org"})
|
|
61
|
+
expect(subject.open?(context)).to be(false)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context 'for properties that have symbol keys' do
|
|
66
|
+
it 'returns true when expression evalutes to true' do
|
|
67
|
+
expression = Flipper.property(:type).eq("User")
|
|
68
|
+
context = context(expression.value, properties: {type: "User"})
|
|
69
|
+
expect(subject.open?(context)).to be(true)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'returns false when expression evaluates to false' do
|
|
73
|
+
expression = Flipper.property(:type).eq("User")
|
|
74
|
+
context = context(expression.value, properties: {type: "Org"})
|
|
75
|
+
expect(subject.open?(context)).to be(false)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '#protects?' do
|
|
81
|
+
it 'returns true for Flipper::Expression' do
|
|
82
|
+
expression = Flipper.number(20).eq(20)
|
|
83
|
+
expect(subject.protects?(expression)).to be(true)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'returns true for Hash' do
|
|
87
|
+
expression = Flipper.number(20).eq(20)
|
|
88
|
+
expect(subject.protects?(expression.value)).to be(true)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'returns false for other things' do
|
|
92
|
+
expect(subject.protects?(false)).to be(false)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe '#wrap' do
|
|
97
|
+
it 'returns self for Flipper::Expression' do
|
|
98
|
+
expression = Flipper.number(20).eq(20)
|
|
99
|
+
expect(subject.wrap(expression)).to be(expression)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'returns Flipper::Expression for Hash' do
|
|
103
|
+
expression = Flipper.number(20).eq(20)
|
|
104
|
+
expect(subject.wrap(expression.value)).to be_instance_of(Flipper::Expression)
|
|
105
|
+
expect(subject.wrap(expression.value)).to eq(expression)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -2,12 +2,11 @@ require 'flipper/identifier'
|
|
|
2
2
|
|
|
3
3
|
RSpec.describe Flipper::Identifier do
|
|
4
4
|
describe '#flipper_id' do
|
|
5
|
-
class User < Struct.new(:id)
|
|
6
|
-
include Flipper::Identifier
|
|
7
|
-
end
|
|
8
|
-
|
|
9
5
|
it 'uses class name and id' do
|
|
10
|
-
|
|
6
|
+
class BlahBlah < Struct.new(:id)
|
|
7
|
+
include Flipper::Identifier
|
|
8
|
+
end
|
|
9
|
+
expect(BlahBlah.new(5).flipper_id).to eq('BlahBlah;5')
|
|
11
10
|
end
|
|
12
11
|
end
|
|
13
12
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'logger'
|
|
2
|
-
require '
|
|
2
|
+
require 'active_support/core_ext/object/blank'
|
|
3
3
|
require 'flipper/instrumentation/log_subscriber'
|
|
4
|
+
require 'flipper/adapters/instrumented'
|
|
4
5
|
|
|
5
6
|
begin
|
|
6
7
|
require 'active_support/isolated_execution_state'
|
|
@@ -8,6 +9,9 @@ rescue LoadError
|
|
|
8
9
|
# ActiveSupport::IsolatedExecutionState is only available in Rails 5.2+
|
|
9
10
|
end
|
|
10
11
|
|
|
12
|
+
# Don't log in other tests, we'll manually re-attach when this one starts
|
|
13
|
+
Flipper::Instrumentation::LogSubscriber.detach
|
|
14
|
+
|
|
11
15
|
RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
12
16
|
let(:adapter) do
|
|
13
17
|
memory = Flipper::Adapters::Memory.new
|
|
@@ -32,8 +36,12 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
|
32
36
|
described_class.logger = nil
|
|
33
37
|
end
|
|
34
38
|
|
|
39
|
+
before(:all) do
|
|
40
|
+
described_class.attach
|
|
41
|
+
end
|
|
42
|
+
|
|
35
43
|
after(:all) do
|
|
36
|
-
|
|
44
|
+
described_class.detach
|
|
37
45
|
end
|
|
38
46
|
|
|
39
47
|
let(:log) { @io.string }
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require 'flipper/adapters/instrumented'
|
|
2
2
|
require 'flipper/instrumentation/statsd'
|
|
3
|
-
require 'statsd'
|
|
4
3
|
|
|
5
4
|
begin
|
|
6
5
|
require 'active_support/isolated_execution_state'
|
|
@@ -19,7 +18,7 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
|
|
|
19
18
|
Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
let(:user) {
|
|
21
|
+
let(:user) { Flipper::Actor.new('1') }
|
|
23
22
|
|
|
24
23
|
before do
|
|
25
24
|
described_class.client = statsd_client
|
|
@@ -78,4 +77,19 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
|
|
|
78
77
|
flipper[:stats].disable(user)
|
|
79
78
|
assert_timer 'flipper.adapter.memory.disable'
|
|
80
79
|
end
|
|
80
|
+
|
|
81
|
+
context 'when client is nil' do
|
|
82
|
+
before do
|
|
83
|
+
described_class.client = nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'does not raise error' do
|
|
87
|
+
expect { flipper[:stats].enable(user) }.not_to raise_error
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'does not update metrics' do
|
|
91
|
+
flipper[:stats].enable(user)
|
|
92
|
+
expect(socket.buffer).to be_empty
|
|
93
|
+
end
|
|
94
|
+
end
|
|
81
95
|
end
|