flipper 0.26.0 → 1.1.0
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 +19 -13
- data/.github/workflows/examples.yml +32 -15
- data/Changelog.md +294 -154
- data/Gemfile +15 -10
- data/README.md +13 -11
- data/benchmark/enabled_ips.rb +10 -0
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/benchmark/enabled_profile.rb +20 -0
- data/benchmark/instrumentation_ips.rb +21 -0
- data/benchmark/typecast_ips.rb +27 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- 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/threaded.rb +33 -0
- data/examples/dsl.rb +1 -15
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/expressions.rb +213 -0
- data/examples/mirroring.rb +59 -0
- data/examples/strict.rb +18 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +3 -5
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +33 -7
- data/lib/flipper/adapter_builder.rb +44 -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 +26 -7
- data/lib/flipper/adapters/http/error.rb +1 -1
- data/lib/flipper/adapters/http.rb +29 -16
- data/lib/flipper/adapters/instrumented.rb +25 -6
- data/lib/flipper/adapters/memoizable.rb +33 -21
- data/lib/flipper/adapters/memory.rb +81 -46
- data/lib/flipper/adapters/operation_logger.rb +16 -7
- data/lib/flipper/adapters/poll/poller.rb +2 -125
- data/lib/flipper/adapters/poll.rb +5 -3
- data/lib/flipper/adapters/pstore.rb +17 -11
- data/lib/flipper/adapters/read_only.rb +4 -4
- data/lib/flipper/adapters/strict.rb +47 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
- data/lib/flipper/adapters/sync.rb +0 -4
- data/lib/flipper/cloud/configuration.rb +258 -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/routes.rb +14 -0
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
- data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -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 +98 -0
- data/lib/flipper/cloud/telemetry.rb +183 -0
- data/lib/flipper/cloud.rb +53 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +46 -45
- data/lib/flipper/engine.rb +88 -0
- data/lib/flipper/errors.rb +3 -3
- data/lib/flipper/export.rb +26 -0
- data/lib/flipper/exporter.rb +17 -0
- data/lib/flipper/exporters/json/export.rb +32 -0
- data/lib/flipper/exporters/json/v1.rb +33 -0
- 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 +11 -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 +87 -26
- data/lib/flipper/feature_check_context.rb +10 -6
- data/lib/flipper/gate.rb +13 -11
- data/lib/flipper/gate_values.rb +5 -18
- data/lib/flipper/gates/actor.rb +10 -17
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/gates/group.rb +5 -7
- data/lib/flipper/gates/percentage_of_actors.rb +10 -13
- data/lib/flipper/gates/percentage_of_time.rb +1 -2
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/metadata.rb +5 -1
- data/lib/flipper/middleware/memoizer.rb +30 -14
- data/lib/flipper/poller.rb +117 -0
- data/lib/flipper/serializers/gzip.rb +24 -0
- data/lib/flipper/serializers/json.rb +19 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +95 -54
- data/lib/flipper/test/shared_adapter_test.rb +91 -48
- data/lib/flipper/typecast.rb +56 -15
- data/lib/flipper/types/actor.rb +13 -13
- data/lib/flipper/types/group.rb +4 -4
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +47 -10
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/adapter_builder_spec.rb +73 -0
- data/spec/flipper/adapter_spec.rb +30 -2
- data/spec/flipper/adapters/dual_write_spec.rb +2 -2
- data/spec/flipper/adapters/http_spec.rb +64 -8
- data/spec/flipper/adapters/instrumented_spec.rb +29 -11
- data/spec/flipper/adapters/memoizable_spec.rb +51 -31
- data/spec/flipper/adapters/memory_spec.rb +14 -3
- data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
- data/spec/flipper/adapters/read_only_spec.rb +32 -17
- data/spec/flipper/adapters/strict_spec.rb +62 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
- data/spec/flipper/cloud/configuration_spec.rb +252 -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/telemetry/backoff_policy_spec.rb +108 -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 +156 -0
- data/spec/flipper/cloud_spec.rb +180 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +54 -73
- data/spec/flipper/engine_spec.rb +291 -0
- data/spec/flipper/export_spec.rb +13 -0
- data/spec/flipper/exporter_spec.rb +16 -0
- data/spec/flipper/exporters/json/export_spec.rb +60 -0
- data/spec/flipper/exporters/json/v1_spec.rb +33 -0
- 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_check_context_spec.rb +17 -17
- data/spec/flipper/feature_spec.rb +436 -33
- data/spec/flipper/gate_values_spec.rb +2 -33
- data/spec/flipper/gates/boolean_spec.rb +1 -1
- data/spec/flipper/gates/expression_spec.rb +108 -0
- data/spec/flipper/gates/group_spec.rb +2 -3
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
- data/spec/flipper/middleware/memoizer_spec.rb +67 -0
- data/spec/flipper/poller_spec.rb +47 -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 +121 -6
- data/spec/flipper/types/actor_spec.rb +63 -46
- data/spec/flipper/types/group_spec.rb +2 -2
- data/spec/flipper_integration_spec.rb +168 -58
- data/spec/flipper_spec.rb +92 -28
- data/spec/spec_helper.rb +6 -13
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/climate_control.rb +7 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +11 -3
- metadata +166 -13
- data/.github/workflows/release.yml +0 -44
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
@@ -0,0 +1,73 @@
|
|
1
|
+
RSpec.describe Flipper::AdapterBuilder do
|
2
|
+
describe "#initialize" do
|
3
|
+
it "instance_eval's block with no arg" do
|
4
|
+
called = false
|
5
|
+
self_in_block = nil
|
6
|
+
|
7
|
+
described_class.new do
|
8
|
+
called = true
|
9
|
+
self_in_block = self
|
10
|
+
end
|
11
|
+
|
12
|
+
expect(self_in_block).to be_instance_of(described_class)
|
13
|
+
expect(called).to be(true)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "evals block with arg" do
|
17
|
+
called = false
|
18
|
+
self_outside_block = self
|
19
|
+
self_in_block = nil
|
20
|
+
|
21
|
+
described_class.new do |arg|
|
22
|
+
called = true
|
23
|
+
self_in_block = self
|
24
|
+
expect(arg).to be_instance_of(described_class)
|
25
|
+
end
|
26
|
+
|
27
|
+
expect(self_in_block).to be(self_outside_block)
|
28
|
+
expect(called).to be(true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#use" do
|
33
|
+
it "wraps the store adapter with the given adapter" do
|
34
|
+
subject.use(Flipper::Adapters::Memoizable)
|
35
|
+
subject.use(Flipper::Adapters::Strict, :warn)
|
36
|
+
|
37
|
+
memoizable_adapter = subject.to_adapter
|
38
|
+
strict_adapter = memoizable_adapter.adapter
|
39
|
+
memory_adapter = strict_adapter.adapter
|
40
|
+
|
41
|
+
|
42
|
+
expect(memoizable_adapter).to be_instance_of(Flipper::Adapters::Memoizable)
|
43
|
+
expect(strict_adapter).to be_instance_of(Flipper::Adapters::Strict)
|
44
|
+
expect(strict_adapter.handler).to be(Flipper::Adapters::Strict::HANDLERS.fetch(:warn))
|
45
|
+
expect(memory_adapter).to be_instance_of(Flipper::Adapters::Memory)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "passes block to adapter initializer" do
|
49
|
+
expected_block = lambda {}
|
50
|
+
adapter_class = double('adapter class')
|
51
|
+
|
52
|
+
subject.use(adapter_class, &expected_block)
|
53
|
+
|
54
|
+
expect(adapter_class).to receive(:new) { |&block| expect(block).to be(expected_block) }.and_return(:adapter)
|
55
|
+
expect(subject.to_adapter).to be(:adapter)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#store" do
|
60
|
+
it "defaults to memory adapter" do
|
61
|
+
expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::Memory)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "only saves one store" do
|
65
|
+
require "flipper/adapters/pstore"
|
66
|
+
subject.store(Flipper::Adapters::PStore)
|
67
|
+
expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::PStore)
|
68
|
+
|
69
|
+
subject.store(Flipper::Adapters::Memory)
|
70
|
+
expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::Memory)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -6,6 +6,7 @@ RSpec.describe Flipper::Adapter do
|
|
6
6
|
boolean: nil,
|
7
7
|
groups: Set.new,
|
8
8
|
actors: Set.new,
|
9
|
+
expression: nil,
|
9
10
|
percentage_of_actors: nil,
|
10
11
|
percentage_of_time: nil,
|
11
12
|
}
|
@@ -30,9 +31,9 @@ RSpec.describe Flipper::Adapter do
|
|
30
31
|
end
|
31
32
|
|
32
33
|
describe '#import' do
|
33
|
-
it 'returns
|
34
|
+
it 'returns true' do
|
34
35
|
result = destination_flipper.import(source_flipper)
|
35
|
-
expect(result).to be(
|
36
|
+
expect(result).to be(true)
|
36
37
|
end
|
37
38
|
|
38
39
|
it 'can import from one adapter to another' do
|
@@ -114,5 +115,32 @@ RSpec.describe Flipper::Adapter do
|
|
114
115
|
destination_flipper.import(source_flipper)
|
115
116
|
expect(destination_flipper.features.map(&:key)).to eq([])
|
116
117
|
end
|
118
|
+
|
119
|
+
it 'can import an export' do
|
120
|
+
source_flipper.enable(:search)
|
121
|
+
source_flipper.enable(:google_analytics, Flipper::Actor.new("User;1"))
|
122
|
+
|
123
|
+
destination_flipper.import(source_flipper.export)
|
124
|
+
|
125
|
+
feature = destination_flipper[:search]
|
126
|
+
expect(feature.boolean_value).to be(true)
|
127
|
+
|
128
|
+
feature = destination_flipper[:google_analytics]
|
129
|
+
expect(feature.actors_value).to eq(Set["User;1"])
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#export" do
|
134
|
+
it "exports features" do
|
135
|
+
source_flipper.enable(:search)
|
136
|
+
export = source_flipper.export
|
137
|
+
expect(export.features.dig("search", :boolean)).to eq("true")
|
138
|
+
end
|
139
|
+
|
140
|
+
it "exports with arguments" do
|
141
|
+
source_flipper.enable(:search)
|
142
|
+
export = source_flipper.export(format: :json, version: 1)
|
143
|
+
expect(export.features.dig("search", :boolean)).to eq("true")
|
144
|
+
end
|
117
145
|
end
|
118
146
|
end
|
@@ -55,14 +55,14 @@ RSpec.describe Flipper::Adapters::DualWrite do
|
|
55
55
|
|
56
56
|
it 'updates remote and local for #enable' do
|
57
57
|
feature = sync[:search]
|
58
|
-
subject.enable feature, feature.gate(:boolean),
|
58
|
+
subject.enable feature, feature.gate(:boolean), Flipper::Types::Boolean.new(true)
|
59
59
|
expect(remote_adapter.count(:enable)).to be(1)
|
60
60
|
expect(local_adapter.count(:enable)).to be(1)
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'updates remote and local for #disable' do
|
64
64
|
feature = sync[:search]
|
65
|
-
subject.disable feature, feature.gate(:boolean),
|
65
|
+
subject.disable feature, feature.gate(:boolean), Flipper::Types::Boolean.new(false)
|
66
66
|
expect(remote_adapter.count(:disable)).to be(1)
|
67
67
|
expect(local_adapter.count(:disable)).to be(1)
|
68
68
|
end
|
@@ -52,6 +52,28 @@ RSpec.describe Flipper::Adapters::Http do
|
|
52
52
|
expect(flipper[:search].disable_group(:some_made_up_group)).to be(true)
|
53
53
|
expect(flipper[:search].groups_value).to eq(Set.new)
|
54
54
|
end
|
55
|
+
|
56
|
+
it "can import" do
|
57
|
+
adapter = Flipper::Adapters::Memory.new
|
58
|
+
source_flipper = Flipper.new(adapter)
|
59
|
+
source_flipper.enable_percentage_of_actors :search, 10
|
60
|
+
source_flipper.enable_percentage_of_time :search, 15
|
61
|
+
source_flipper.enable_actor :search, Flipper::Actor.new('User;1')
|
62
|
+
source_flipper.enable_actor :search, Flipper::Actor.new('User;100')
|
63
|
+
source_flipper.enable_group :search, :admins
|
64
|
+
source_flipper.enable_group :search, :employees
|
65
|
+
source_flipper.enable :plausible
|
66
|
+
source_flipper.disable :google_analytics
|
67
|
+
|
68
|
+
flipper = Flipper.new(subject)
|
69
|
+
flipper.import(source_flipper)
|
70
|
+
expect(flipper[:search].percentage_of_actors_value).to be(10)
|
71
|
+
expect(flipper[:search].percentage_of_time_value).to be(15)
|
72
|
+
expect(flipper[:search].actors_value).to eq(Set["User;1", "User;100"])
|
73
|
+
expect(flipper[:search].groups_value).to eq(Set["admins", "employees"])
|
74
|
+
expect(flipper[:plausible].boolean_value).to be(true)
|
75
|
+
expect(flipper[:google_analytics].boolean_value).to be(false)
|
76
|
+
end
|
55
77
|
end
|
56
78
|
|
57
79
|
it "sends default headers" do
|
@@ -62,16 +84,50 @@ RSpec.describe Flipper::Adapters::Http do
|
|
62
84
|
}
|
63
85
|
stub_request(:get, "http://app.com/flipper/features/feature_panel")
|
64
86
|
.with(headers: headers)
|
65
|
-
.to_return(status: 404
|
87
|
+
.to_return(status: 404)
|
66
88
|
|
67
89
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
68
90
|
adapter.get(flipper[:feature_panel])
|
69
91
|
end
|
70
92
|
|
93
|
+
it "sends framework versions" do
|
94
|
+
stub_const("Rails", double(version: "7.1.0"))
|
95
|
+
stub_const("Sinatra::VERSION", "3.1.0")
|
96
|
+
stub_const("Hanami::VERSION", "0.7.2")
|
97
|
+
|
98
|
+
headers = {
|
99
|
+
"Client-Framework" => ["rails=7.1.0", "sinatra=3.1.0", "hanami=0.7.2"]
|
100
|
+
}
|
101
|
+
|
102
|
+
stub_request(:get, "http://app.com/flipper/features/feature_panel")
|
103
|
+
.with(headers: headers)
|
104
|
+
.to_return(status: 404)
|
105
|
+
|
106
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
107
|
+
adapter.get(flipper[:feature_panel])
|
108
|
+
end
|
109
|
+
|
110
|
+
it "does not send undefined framework versions" do
|
111
|
+
stub_const("Rails", double(version: "7.1.0"))
|
112
|
+
stub_const("Sinatra::VERSION", "3.1.0")
|
113
|
+
|
114
|
+
headers = {
|
115
|
+
"Client-Framework" => ["rails=7.1.0", "sinatra=3.1.0"]
|
116
|
+
}
|
117
|
+
|
118
|
+
stub_request(:get, "http://app.com/flipper/features/feature_panel")
|
119
|
+
.with(headers: headers)
|
120
|
+
.to_return(status: 404)
|
121
|
+
|
122
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
123
|
+
adapter.get(flipper[:feature_panel])
|
124
|
+
end
|
125
|
+
|
126
|
+
|
71
127
|
describe "#get" do
|
72
128
|
it "raises error when not successful response" do
|
73
129
|
stub_request(:get, "http://app.com/flipper/features/feature_panel")
|
74
|
-
.to_return(status: 503
|
130
|
+
.to_return(status: 503)
|
75
131
|
|
76
132
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
77
133
|
expect {
|
@@ -82,8 +138,8 @@ RSpec.describe Flipper::Adapters::Http do
|
|
82
138
|
|
83
139
|
describe "#get_multi" do
|
84
140
|
it "raises error when not successful response" do
|
85
|
-
stub_request(:get, "http://app.com/flipper/features?keys=feature_panel")
|
86
|
-
.to_return(status: 503
|
141
|
+
stub_request(:get, "http://app.com/flipper/features?keys=feature_panel&exclude_gate_names=true")
|
142
|
+
.to_return(status: 503)
|
87
143
|
|
88
144
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
89
145
|
expect {
|
@@ -94,8 +150,8 @@ RSpec.describe Flipper::Adapters::Http do
|
|
94
150
|
|
95
151
|
describe "#get_all" do
|
96
152
|
it "raises error when not successful response" do
|
97
|
-
stub_request(:get, "http://app.com/flipper/features")
|
98
|
-
.to_return(status: 503
|
153
|
+
stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
|
154
|
+
.to_return(status: 503)
|
99
155
|
|
100
156
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
101
157
|
expect {
|
@@ -106,8 +162,8 @@ RSpec.describe Flipper::Adapters::Http do
|
|
106
162
|
|
107
163
|
describe "#features" do
|
108
164
|
it "raises error when not successful response" do
|
109
|
-
stub_request(:get, "http://app.com/flipper/features")
|
110
|
-
.to_return(status: 503
|
165
|
+
stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
|
166
|
+
.to_return(status: 503)
|
111
167
|
|
112
168
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
113
169
|
expect {
|
@@ -8,7 +8,7 @@ RSpec.describe Flipper::Adapters::Instrumented do
|
|
8
8
|
|
9
9
|
let(:feature) { flipper[:stats] }
|
10
10
|
let(:gate) { feature.gate(:percentage_of_actors) }
|
11
|
-
let(:thing) {
|
11
|
+
let(:thing) { Flipper::Types::PercentageOfActors.new(22) }
|
12
12
|
|
13
13
|
subject do
|
14
14
|
described_class.new(adapter, instrumenter: instrumenter)
|
@@ -16,16 +16,6 @@ RSpec.describe Flipper::Adapters::Instrumented do
|
|
16
16
|
|
17
17
|
it_should_behave_like 'a flipper adapter'
|
18
18
|
|
19
|
-
it 'forwards missing methods to underlying adapter' do
|
20
|
-
adapter = Class.new do
|
21
|
-
def foo
|
22
|
-
:foo
|
23
|
-
end
|
24
|
-
end.new
|
25
|
-
instrumented = described_class.new(adapter)
|
26
|
-
expect(instrumented.foo).to eq(:foo)
|
27
|
-
end
|
28
|
-
|
29
19
|
describe '#name' do
|
30
20
|
it 'is instrumented' do
|
31
21
|
expect(subject.name).to be(:instrumented)
|
@@ -146,4 +136,32 @@ RSpec.describe Flipper::Adapters::Instrumented do
|
|
146
136
|
expect(event.payload[:result]).to be(result)
|
147
137
|
end
|
148
138
|
end
|
139
|
+
|
140
|
+
describe '#import' do
|
141
|
+
it 'records instrumentation' do
|
142
|
+
result = subject.import(Flipper::Adapters::Memory.new)
|
143
|
+
|
144
|
+
event = instrumenter.events.last
|
145
|
+
expect(event).not_to be_nil
|
146
|
+
expect(event.name).to eq('adapter_operation.flipper')
|
147
|
+
expect(event.payload[:operation]).to eq(:import)
|
148
|
+
expect(event.payload[:adapter_name]).to eq(:memory)
|
149
|
+
expect(event.payload[:result]).to be(result)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '#export' do
|
154
|
+
it 'records instrumentation' do
|
155
|
+
result = subject.export(format: :json, version: 1)
|
156
|
+
|
157
|
+
event = instrumenter.events.last
|
158
|
+
expect(event).not_to be_nil
|
159
|
+
expect(event.name).to eq('adapter_operation.flipper')
|
160
|
+
expect(event.payload[:operation]).to eq(:export)
|
161
|
+
expect(event.payload[:adapter_name]).to eq(:memory)
|
162
|
+
expect(event.payload[:format]).to be(:json)
|
163
|
+
expect(event.payload[:version]).to be(1)
|
164
|
+
expect(event.payload[:result]).to be(result)
|
165
|
+
end
|
166
|
+
end
|
149
167
|
end
|
@@ -2,7 +2,7 @@ require 'flipper/adapters/memoizable'
|
|
2
2
|
require 'flipper/adapters/operation_logger'
|
3
3
|
|
4
4
|
RSpec.describe Flipper::Adapters::Memoizable do
|
5
|
-
let(:features_key) {
|
5
|
+
let(:features_key) { :flipper_features }
|
6
6
|
let(:adapter) { Flipper::Adapters::Memory.new }
|
7
7
|
let(:flipper) { Flipper.new(adapter) }
|
8
8
|
let(:cache) { {} }
|
@@ -11,16 +11,6 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
11
11
|
|
12
12
|
it_should_behave_like 'a flipper adapter'
|
13
13
|
|
14
|
-
it 'forwards missing methods to underlying adapter' do
|
15
|
-
adapter = Class.new do
|
16
|
-
def foo
|
17
|
-
:foo
|
18
|
-
end
|
19
|
-
end.new
|
20
|
-
memoizable = described_class.new(adapter)
|
21
|
-
expect(memoizable.foo).to eq(:foo)
|
22
|
-
end
|
23
|
-
|
24
14
|
describe '#name' do
|
25
15
|
it 'is instrumented' do
|
26
16
|
expect(subject.name).to be(:memoizable)
|
@@ -64,7 +54,7 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
64
54
|
it 'memoizes feature' do
|
65
55
|
feature = flipper[:stats]
|
66
56
|
result = subject.get(feature)
|
67
|
-
expect(cache[
|
57
|
+
expect(cache["feature/#{feature.key}"]).to be(result)
|
68
58
|
end
|
69
59
|
end
|
70
60
|
|
@@ -93,8 +83,8 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
93
83
|
features = names.map { |name| flipper[name] }
|
94
84
|
results = subject.get_multi(features)
|
95
85
|
features.each do |feature|
|
96
|
-
expect(cache[
|
97
|
-
expect(cache[
|
86
|
+
expect(cache["feature/#{feature.key}"]).not_to be(nil)
|
87
|
+
expect(cache["feature/#{feature.key}"]).to be(results[feature.key])
|
98
88
|
end
|
99
89
|
end
|
100
90
|
end
|
@@ -125,10 +115,10 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
125
115
|
features = names.map { |name| flipper[name].tap(&:enable) }
|
126
116
|
results = subject.get_all
|
127
117
|
features.each do |feature|
|
128
|
-
expect(cache[
|
129
|
-
expect(cache[
|
118
|
+
expect(cache["feature/#{feature.key}"]).not_to be(nil)
|
119
|
+
expect(cache["feature/#{feature.key}"]).to be(results[feature.key])
|
130
120
|
end
|
131
|
-
expect(cache[
|
121
|
+
expect(cache[:flipper_features]).to eq(names.map(&:to_s).to_set)
|
132
122
|
end
|
133
123
|
|
134
124
|
it 'only calls get_all once for memoized adapter' do
|
@@ -198,9 +188,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
198
188
|
it 'unmemoizes feature' do
|
199
189
|
feature = flipper[:stats]
|
200
190
|
gate = feature.gate(:boolean)
|
201
|
-
cache[
|
202
|
-
subject.enable(feature, gate,
|
203
|
-
expect(cache[
|
191
|
+
cache["feature/#{feature.key}"] = { some: 'thing' }
|
192
|
+
subject.enable(feature, gate, Flipper::Types::Boolean.new)
|
193
|
+
expect(cache["feature/#{feature.key}"]).to be_nil
|
204
194
|
end
|
205
195
|
end
|
206
196
|
|
@@ -212,8 +202,8 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
212
202
|
it 'returns result' do
|
213
203
|
feature = flipper[:stats]
|
214
204
|
gate = feature.gate(:boolean)
|
215
|
-
result = subject.enable(feature, gate,
|
216
|
-
adapter_result = adapter.enable(feature, gate,
|
205
|
+
result = subject.enable(feature, gate, Flipper::Types::Boolean.new)
|
206
|
+
adapter_result = adapter.enable(feature, gate, Flipper::Types::Boolean.new)
|
217
207
|
expect(result).to eq(adapter_result)
|
218
208
|
end
|
219
209
|
end
|
@@ -228,9 +218,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
228
218
|
it 'unmemoizes feature' do
|
229
219
|
feature = flipper[:stats]
|
230
220
|
gate = feature.gate(:boolean)
|
231
|
-
cache[
|
232
|
-
subject.disable(feature, gate,
|
233
|
-
expect(cache[
|
221
|
+
cache["feature/#{feature.key}"] = { some: 'thing' }
|
222
|
+
subject.disable(feature, gate, Flipper::Types::Boolean.new)
|
223
|
+
expect(cache["feature/#{feature.key}"]).to be_nil
|
234
224
|
end
|
235
225
|
end
|
236
226
|
|
@@ -242,13 +232,43 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
242
232
|
it 'returns result' do
|
243
233
|
feature = flipper[:stats]
|
244
234
|
gate = feature.gate(:boolean)
|
245
|
-
result = subject.disable(feature, gate,
|
246
|
-
adapter_result = adapter.disable(feature, gate,
|
235
|
+
result = subject.disable(feature, gate, Flipper::Types::Boolean.new)
|
236
|
+
adapter_result = adapter.disable(feature, gate, Flipper::Types::Boolean.new)
|
247
237
|
expect(result).to eq(adapter_result)
|
248
238
|
end
|
249
239
|
end
|
250
240
|
end
|
251
241
|
|
242
|
+
describe "#import" do
|
243
|
+
context "with memoization enabled" do
|
244
|
+
before do
|
245
|
+
subject.memoize = true
|
246
|
+
end
|
247
|
+
|
248
|
+
it "unmemoizes features" do
|
249
|
+
cache[:foo] = "bar"
|
250
|
+
flipper[:stats].enable
|
251
|
+
flipper[:search].disable
|
252
|
+
subject.import(Flipper::Adapters::Memory.new)
|
253
|
+
expect(cache).to be_empty
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context "with memoization disabled" do
|
258
|
+
before do
|
259
|
+
subject.memoize = false
|
260
|
+
end
|
261
|
+
|
262
|
+
it "does not unmemoize features" do
|
263
|
+
cache[:foo] = "bar"
|
264
|
+
flipper[:stats].enable
|
265
|
+
flipper[:search].disable
|
266
|
+
subject.import(Flipper::Adapters::Memory.new)
|
267
|
+
expect(cache).not_to be_empty
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
252
272
|
describe '#features' do
|
253
273
|
context 'with memoization enabled' do
|
254
274
|
before do
|
@@ -312,9 +332,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
312
332
|
|
313
333
|
it 'unmemoizes the feature' do
|
314
334
|
feature = flipper[:stats]
|
315
|
-
cache[
|
335
|
+
cache["feature/#{feature.key}"] = { some: 'thing' }
|
316
336
|
subject.remove(feature)
|
317
|
-
expect(cache[
|
337
|
+
expect(cache["feature/#{feature.key}"]).to be_nil
|
318
338
|
end
|
319
339
|
end
|
320
340
|
|
@@ -337,9 +357,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
337
357
|
|
338
358
|
it 'unmemoizes feature' do
|
339
359
|
feature = flipper[:stats]
|
340
|
-
cache[
|
360
|
+
cache["feature/#{feature.key}"] = { some: 'thing' }
|
341
361
|
subject.clear(feature)
|
342
|
-
expect(cache[
|
362
|
+
expect(cache["feature/#{feature.key}"]).to be_nil
|
343
363
|
end
|
344
364
|
end
|
345
365
|
|
@@ -1,8 +1,17 @@
|
|
1
1
|
RSpec.describe Flipper::Adapters::Memory do
|
2
2
|
let(:source) { {} }
|
3
|
-
subject { described_class.new(source) }
|
4
3
|
|
5
|
-
|
4
|
+
context 'threadsafe: true' do
|
5
|
+
subject { described_class.new(source, threadsafe: true) }
|
6
|
+
|
7
|
+
it_should_behave_like 'a flipper adapter'
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'threadsafe: false' do
|
11
|
+
subject { described_class.new(source, threadsafe: false) }
|
12
|
+
|
13
|
+
it_should_behave_like 'a flipper adapter'
|
14
|
+
end
|
6
15
|
|
7
16
|
it "can initialize from big hash" do
|
8
17
|
flipper = Flipper.new(subject)
|
@@ -14,7 +23,9 @@ RSpec.describe Flipper::Adapters::Memory do
|
|
14
23
|
flipper.enable_actor :following, Flipper::Actor.new('3')
|
15
24
|
flipper.enable_group :following, Flipper::Types::Group.new(:staff)
|
16
25
|
|
17
|
-
|
26
|
+
dup = described_class.new(subject.get_all)
|
27
|
+
|
28
|
+
expect(dup.get_all).to eq({
|
18
29
|
"subscriptions" => subject.default_config.merge(boolean: "true"),
|
19
30
|
"search" => subject.default_config,
|
20
31
|
"logging" => subject.default_config.merge(:percentage_of_time => "30"),
|
@@ -18,16 +18,6 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
18
18
|
expect(output).to match(/@adapter=#<Flipper::Adapters::Memory/)
|
19
19
|
end
|
20
20
|
|
21
|
-
it 'forwards missing methods to underlying adapter' do
|
22
|
-
adapter = Class.new do
|
23
|
-
def foo
|
24
|
-
:foo
|
25
|
-
end
|
26
|
-
end.new
|
27
|
-
operation_logger = described_class.new(adapter)
|
28
|
-
expect(operation_logger.foo).to eq(:foo)
|
29
|
-
end
|
30
|
-
|
31
21
|
describe '#get' do
|
32
22
|
before do
|
33
23
|
@feature = flipper[:stats]
|
@@ -47,7 +37,7 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
47
37
|
before do
|
48
38
|
@feature = flipper[:stats]
|
49
39
|
@gate = @feature.gate(:boolean)
|
50
|
-
@thing =
|
40
|
+
@thing = Flipper::Types::Boolean.new
|
51
41
|
@result = subject.enable(@feature, @gate, @thing)
|
52
42
|
end
|
53
43
|
|
@@ -64,7 +54,7 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
64
54
|
before do
|
65
55
|
@feature = flipper[:stats]
|
66
56
|
@gate = @feature.gate(:boolean)
|
67
|
-
@thing =
|
57
|
+
@thing = Flipper::Types::Boolean.new
|
68
58
|
@result = subject.disable(@feature, @gate, @thing)
|
69
59
|
end
|
70
60
|
|
@@ -106,4 +96,33 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
106
96
|
expect(@result).to eq(adapter.add(@feature))
|
107
97
|
end
|
108
98
|
end
|
99
|
+
|
100
|
+
describe '#import' do
|
101
|
+
before do
|
102
|
+
@source = Flipper::Adapters::Memory.new
|
103
|
+
@result = subject.import(@source)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'logs operation' do
|
107
|
+
expect(subject.count(:import)).to be(1)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'returns result' do
|
111
|
+
expect(@result).to eq(adapter.import(@source))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#export' do
|
116
|
+
before do
|
117
|
+
@result = subject.export(format: :json, version: 1)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'logs operation' do
|
121
|
+
expect(subject.count(:export)).to be(1)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'returns result' do
|
125
|
+
expect(@result).to eq(adapter.export(format: :json, version: 1))
|
126
|
+
end
|
127
|
+
end
|
109
128
|
end
|
@@ -5,11 +5,12 @@ RSpec.describe Flipper::Adapters::ReadOnly do
|
|
5
5
|
let(:flipper) { Flipper.new(subject) }
|
6
6
|
let(:feature) { flipper[:stats] }
|
7
7
|
|
8
|
-
let(:boolean_gate)
|
9
|
-
let(:group_gate)
|
10
|
-
let(:actor_gate)
|
11
|
-
let(:
|
12
|
-
let(:
|
8
|
+
let(:boolean_gate) { feature.gate(:boolean) }
|
9
|
+
let(:group_gate) { feature.gate(:group) }
|
10
|
+
let(:actor_gate) { feature.gate(:actor) }
|
11
|
+
let(:expression_gate) { feature.gate(:expression) }
|
12
|
+
let(:actors_gate) { feature.gate(:percentage_of_actors) }
|
13
|
+
let(:time_gate) { feature.gate(:percentage_of_time) }
|
13
14
|
|
14
15
|
subject { described_class.new(adapter) }
|
15
16
|
|
@@ -41,18 +42,28 @@ RSpec.describe Flipper::Adapters::ReadOnly do
|
|
41
42
|
end
|
42
43
|
|
43
44
|
it 'can get feature' do
|
45
|
+
expression = Flipper.property(:plan).eq("basic")
|
44
46
|
actor22 = Flipper::Actor.new('22')
|
45
|
-
adapter.enable(feature, boolean_gate,
|
47
|
+
adapter.enable(feature, boolean_gate, Flipper::Types::Boolean.new)
|
46
48
|
adapter.enable(feature, group_gate, flipper.group(:admins))
|
47
|
-
adapter.enable(feature, actor_gate,
|
48
|
-
adapter.enable(feature, actors_gate,
|
49
|
-
adapter.enable(feature, time_gate,
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
adapter.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))
|
50
|
+
adapter.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))
|
51
|
+
adapter.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))
|
52
|
+
adapter.enable(feature, expression_gate, expression)
|
53
|
+
|
54
|
+
expect(subject.get(feature)).to eq({
|
55
|
+
boolean: 'true',
|
56
|
+
groups: Set['admins'],
|
57
|
+
actors: Set['22'],
|
58
|
+
expression: {
|
59
|
+
"Equal" => [
|
60
|
+
{"Property" => ["plan"]},
|
61
|
+
"basic",
|
62
|
+
]
|
63
|
+
},
|
64
|
+
percentage_of_actors: '25',
|
65
|
+
percentage_of_time: '45',
|
66
|
+
})
|
56
67
|
end
|
57
68
|
|
58
69
|
it 'can get features' do
|
@@ -61,6 +72,10 @@ RSpec.describe Flipper::Adapters::ReadOnly do
|
|
61
72
|
expect(subject.features).to eq(Set['stats'])
|
62
73
|
end
|
63
74
|
|
75
|
+
it 'is configured as read only' do
|
76
|
+
expect(subject.read_only?).to eq(true)
|
77
|
+
end
|
78
|
+
|
64
79
|
it 'raises error on add' do
|
65
80
|
expect { subject.add(feature) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
|
66
81
|
end
|
@@ -74,12 +89,12 @@ RSpec.describe Flipper::Adapters::ReadOnly do
|
|
74
89
|
end
|
75
90
|
|
76
91
|
it 'raises error on enable' do
|
77
|
-
expect { subject.enable(feature, boolean_gate,
|
92
|
+
expect { subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new) }
|
78
93
|
.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
|
79
94
|
end
|
80
95
|
|
81
96
|
it 'raises error on disable' do
|
82
|
-
expect { subject.disable(feature, boolean_gate,
|
97
|
+
expect { subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new) }
|
83
98
|
.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
|
84
99
|
end
|
85
100
|
end
|