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
|
@@ -0,0 +1,190 @@
|
|
|
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 actor in context' do
|
|
66
|
+
it 'passes actor to expression context' do
|
|
67
|
+
actor = Flipper::Actor.new("User;1", {type: "User"})
|
|
68
|
+
wrapped_actor = Flipper::Types::Actor.new(actor)
|
|
69
|
+
expression = Flipper.property(:flipper_id).eq("User;1")
|
|
70
|
+
ctx = Flipper::FeatureCheckContext.new(
|
|
71
|
+
feature_name: feature_name,
|
|
72
|
+
values: Flipper::GateValues.new(expression: expression.value),
|
|
73
|
+
actors: [wrapped_actor]
|
|
74
|
+
)
|
|
75
|
+
expect(subject.open?(ctx)).to be(true)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'passes nil actor when no actors provided' do
|
|
79
|
+
expression = Flipper.boolean(true).eq(true)
|
|
80
|
+
ctx = Flipper::FeatureCheckContext.new(
|
|
81
|
+
feature_name: feature_name,
|
|
82
|
+
values: Flipper::GateValues.new(expression: expression.value),
|
|
83
|
+
actors: nil
|
|
84
|
+
)
|
|
85
|
+
expect(subject.open?(ctx)).to be(true)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context 'for properties that have symbol keys' do
|
|
90
|
+
it 'returns true when expression evalutes to true' do
|
|
91
|
+
expression = Flipper.property(:type).eq("User")
|
|
92
|
+
context = context(expression.value, properties: {type: "User"})
|
|
93
|
+
expect(subject.open?(context)).to be(true)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'returns false when expression evaluates to false' do
|
|
97
|
+
expression = Flipper.property(:type).eq("User")
|
|
98
|
+
context = context(expression.value, properties: {type: "Org"})
|
|
99
|
+
expect(subject.open?(context)).to be(false)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context 'for time-based expressions' do
|
|
104
|
+
it 'enables when now is past a scheduled epoch' do
|
|
105
|
+
past_epoch = Time.now.to_i - 86_400
|
|
106
|
+
expression = Flipper.now.gte(Flipper.time(past_epoch))
|
|
107
|
+
expect(subject.open?(context(expression.value))).to be(true)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'does not enable when now is before a future epoch' do
|
|
111
|
+
future_epoch = Time.now.to_i + 86_400
|
|
112
|
+
expression = Flipper.now.gte(Flipper.time(future_epoch))
|
|
113
|
+
expect(subject.open?(context(expression.value))).to be(false)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'enables when now is past a scheduled datetime' do
|
|
117
|
+
past_time = (Time.now.utc - 86_400).iso8601
|
|
118
|
+
expression = Flipper.now.gte(Flipper.time(past_time))
|
|
119
|
+
expect(subject.open?(context(expression.value))).to be(true)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'does not enable when now is before a future datetime' do
|
|
123
|
+
future_time = (Time.now.utc + 86_400).iso8601
|
|
124
|
+
expression = Flipper.now.gte(Flipper.time(future_time))
|
|
125
|
+
expect(subject.open?(context(expression.value))).to be(false)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'enables expiring features with lt' do
|
|
129
|
+
future_time = (Time.now.utc + 86_400).iso8601
|
|
130
|
+
expression = Flipper.now.lt(Flipper.time(future_time))
|
|
131
|
+
expect(subject.open?(context(expression.value))).to be(true)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'disables expired features with lt' do
|
|
135
|
+
past_time = (Time.now.utc - 86_400).iso8601
|
|
136
|
+
expression = Flipper.now.lt(Flipper.time(past_time))
|
|
137
|
+
expect(subject.open?(context(expression.value))).to be(false)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'enables within a time window using all' do
|
|
141
|
+
start_time = (Time.now.utc - 86_400).iso8601
|
|
142
|
+
end_time = (Time.now.utc + 86_400).iso8601
|
|
143
|
+
expression = Flipper.all(
|
|
144
|
+
Flipper.now.gte(Flipper.time(start_time)),
|
|
145
|
+
Flipper.now.lt(Flipper.time(end_time))
|
|
146
|
+
)
|
|
147
|
+
expect(subject.open?(context(expression.value))).to be(true)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'does not enable outside a time window' do
|
|
151
|
+
start_time = (Time.now.utc + 86_400).iso8601
|
|
152
|
+
end_time = (Time.now.utc + 172_800).iso8601
|
|
153
|
+
expression = Flipper.all(
|
|
154
|
+
Flipper.now.gte(Flipper.time(start_time)),
|
|
155
|
+
Flipper.now.lt(Flipper.time(end_time))
|
|
156
|
+
)
|
|
157
|
+
expect(subject.open?(context(expression.value))).to be(false)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe '#protects?' do
|
|
163
|
+
it 'returns true for Flipper::Expression' do
|
|
164
|
+
expression = Flipper.number(20).eq(20)
|
|
165
|
+
expect(subject.protects?(expression)).to be(true)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'returns true for Hash' do
|
|
169
|
+
expression = Flipper.number(20).eq(20)
|
|
170
|
+
expect(subject.protects?(expression.value)).to be(true)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'returns false for other things' do
|
|
174
|
+
expect(subject.protects?(false)).to be(false)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
describe '#wrap' do
|
|
179
|
+
it 'returns self for Flipper::Expression' do
|
|
180
|
+
expression = Flipper.number(20).eq(20)
|
|
181
|
+
expect(subject.wrap(expression)).to be(expression)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'returns Flipper::Expression for Hash' do
|
|
185
|
+
expression = Flipper.number(20).eq(20)
|
|
186
|
+
expect(subject.wrap(expression.value)).to be_instance_of(Flipper::Expression)
|
|
187
|
+
expect(subject.wrap(expression.value)).to eq(expression)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
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
|
|
@@ -2,6 +2,8 @@ require 'rack/test'
|
|
|
2
2
|
require 'active_support/cache'
|
|
3
3
|
require 'flipper/adapters/active_support_cache_store'
|
|
4
4
|
require 'flipper/adapters/operation_logger'
|
|
5
|
+
require 'flipper/adapters/actor_limit'
|
|
6
|
+
require 'flipper/adapters/sync'
|
|
5
7
|
|
|
6
8
|
RSpec.describe Flipper::Middleware::Memoizer do
|
|
7
9
|
include Rack::Test::Methods
|
|
@@ -80,7 +82,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
80
82
|
context 'with preload: true' do
|
|
81
83
|
let(:app) do
|
|
82
84
|
# ensure scoped for builder block, annoying...
|
|
83
|
-
|
|
85
|
+
flipper
|
|
84
86
|
middleware = described_class
|
|
85
87
|
|
|
86
88
|
Rack::Builder.new do
|
|
@@ -141,7 +143,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
141
143
|
context 'with preload specific' do
|
|
142
144
|
let(:app) do
|
|
143
145
|
# ensure scoped for builder block, annoying...
|
|
144
|
-
|
|
146
|
+
flipper
|
|
145
147
|
middleware = described_class
|
|
146
148
|
|
|
147
149
|
Rack::Builder.new do
|
|
@@ -196,10 +198,77 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
196
198
|
end
|
|
197
199
|
end
|
|
198
200
|
|
|
201
|
+
context 'with preload block' do
|
|
202
|
+
let(:app) do
|
|
203
|
+
app = lambda do |_env|
|
|
204
|
+
flipper[:stats].enabled?
|
|
205
|
+
flipper[:stats].enabled?
|
|
206
|
+
flipper[:shiny].enabled?
|
|
207
|
+
flipper[:shiny].enabled?
|
|
208
|
+
[200, {}, []]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
described_class.new(app, preload: ->(request) {
|
|
212
|
+
case request.path
|
|
213
|
+
when "/true"
|
|
214
|
+
true
|
|
215
|
+
when "/specific"
|
|
216
|
+
[:stats]
|
|
217
|
+
else
|
|
218
|
+
false
|
|
219
|
+
end
|
|
220
|
+
})
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
include_examples 'flipper middleware'
|
|
224
|
+
|
|
225
|
+
it 'eagerly caches known features for duration of request if block returns true' do
|
|
226
|
+
flipper[:stats].enable
|
|
227
|
+
flipper[:shiny].enable
|
|
228
|
+
|
|
229
|
+
# clear the log of operations
|
|
230
|
+
adapter.reset
|
|
231
|
+
|
|
232
|
+
get '/true', {}, 'flipper' => flipper
|
|
233
|
+
|
|
234
|
+
expect(adapter.operations.size).to be(1)
|
|
235
|
+
expect(adapter.count(:get_all)).to be(1)
|
|
236
|
+
expect(adapter.count(:get)).to be(0)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'does not eagerly cache known features if block returns false' do
|
|
240
|
+
flipper[:stats].enable
|
|
241
|
+
flipper[:shiny].enable
|
|
242
|
+
|
|
243
|
+
# clear the log of operations
|
|
244
|
+
adapter.reset
|
|
245
|
+
|
|
246
|
+
get '/false', {}, 'flipper' => flipper
|
|
247
|
+
|
|
248
|
+
expect(adapter.operations.size).to be(2)
|
|
249
|
+
expect(adapter.count(:get_all)).to be(0)
|
|
250
|
+
expect(adapter.count(:get)).to be(2)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it 'eagerly caches specified features for duration of request if block returns array of specified features' do
|
|
254
|
+
flipper[:stats].enable
|
|
255
|
+
flipper[:shiny].enable
|
|
256
|
+
|
|
257
|
+
# clear the log of operations
|
|
258
|
+
adapter.reset
|
|
259
|
+
|
|
260
|
+
get '/specific', {}, 'flipper' => flipper
|
|
261
|
+
|
|
262
|
+
expect(adapter.operations.size).to be(2)
|
|
263
|
+
expect(adapter.count(:get_multi)).to be(1)
|
|
264
|
+
expect(adapter.count(:get)).to be(1)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
199
268
|
context 'with multiple instances' do
|
|
200
269
|
let(:app) do
|
|
201
270
|
# ensure scoped for builder block, annoying...
|
|
202
|
-
|
|
271
|
+
flipper
|
|
203
272
|
middleware = described_class
|
|
204
273
|
|
|
205
274
|
Rack::Builder.new do
|
|
@@ -218,7 +287,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
218
287
|
end
|
|
219
288
|
|
|
220
289
|
def get(uri, params = {}, env = {}, &block)
|
|
221
|
-
|
|
290
|
+
capture_output { super(uri, params, env, &block) }
|
|
222
291
|
end
|
|
223
292
|
|
|
224
293
|
include_examples 'flipper middleware'
|
|
@@ -249,7 +318,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
249
318
|
context 'with flipper setup in env' do
|
|
250
319
|
let(:app) do
|
|
251
320
|
# ensure scoped for builder block, annoying...
|
|
252
|
-
|
|
321
|
+
flipper
|
|
253
322
|
middleware = described_class
|
|
254
323
|
|
|
255
324
|
Rack::Builder.new do
|
|
@@ -391,9 +460,8 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
391
460
|
logged_memory = Flipper::Adapters::OperationLogger.new(memory)
|
|
392
461
|
cache = ActiveSupport::Cache::MemoryStore.new
|
|
393
462
|
cache.clear
|
|
394
|
-
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache
|
|
463
|
+
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache)
|
|
395
464
|
logged_cached = Flipper::Adapters::OperationLogger.new(cached)
|
|
396
|
-
memo = {}
|
|
397
465
|
flipper = Flipper.new(logged_cached)
|
|
398
466
|
flipper[:stats].enable
|
|
399
467
|
flipper[:shiny].enable
|
|
@@ -415,4 +483,36 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
415
483
|
expect(logged_memory.count(:get_all)).to be(1)
|
|
416
484
|
end
|
|
417
485
|
end
|
|
486
|
+
|
|
487
|
+
context 'with preload:true and Sync adapter wrapped with ActorLimit' do
|
|
488
|
+
it 'preloads even when remote has more actors than local limit' do
|
|
489
|
+
local = Flipper::Adapters::Memory.new
|
|
490
|
+
remote = Flipper::Adapters::Memory.new
|
|
491
|
+
remote_flipper = Flipper.new(remote)
|
|
492
|
+
|
|
493
|
+
# Remote has more actors than limit allows (actor-only enables, not boolean)
|
|
494
|
+
10.times { |i| remote_flipper[:stats].enable_actor Flipper::Actor.new("User;#{i}") }
|
|
495
|
+
|
|
496
|
+
# Sync adapter will sync from remote to local, then ActorLimit wraps it
|
|
497
|
+
# Use interval: 0 to force sync on every call
|
|
498
|
+
sync_adapter = Flipper::Adapters::Sync.new(local, remote, interval: 0)
|
|
499
|
+
limited_adapter = Flipper::Adapters::ActorLimit.new(sync_adapter, 5)
|
|
500
|
+
test_flipper = Flipper.new(limited_adapter)
|
|
501
|
+
|
|
502
|
+
app = lambda do |env|
|
|
503
|
+
f = env['flipper']
|
|
504
|
+
f[:stats].enabled?
|
|
505
|
+
[200, {}, []]
|
|
506
|
+
end
|
|
507
|
+
middleware = described_class.new(app, preload: true)
|
|
508
|
+
|
|
509
|
+
# Preload should work without raising ActorLimit::LimitExceeded
|
|
510
|
+
expect {
|
|
511
|
+
middleware.call('flipper' => test_flipper)
|
|
512
|
+
}.not_to raise_error
|
|
513
|
+
|
|
514
|
+
# Verify actors were synced (all 10, not just 5)
|
|
515
|
+
expect(test_flipper[:stats].actors_value.size).to eq(10)
|
|
516
|
+
end
|
|
517
|
+
end
|
|
418
518
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
require 'flipper/model/active_record'
|
|
3
|
+
|
|
4
|
+
# Turn off migration logging for specs
|
|
5
|
+
ActiveRecord::Migration.verbose = false
|
|
6
|
+
|
|
7
|
+
RSpec.describe Flipper::Model::ActiveRecord do
|
|
8
|
+
before(:all) do
|
|
9
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before(:each) do
|
|
13
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
|
14
|
+
CREATE TABLE users (
|
|
15
|
+
id integer PRIMARY KEY,
|
|
16
|
+
name string NOT NULL,
|
|
17
|
+
age integer,
|
|
18
|
+
is_confirmed boolean,
|
|
19
|
+
created_at datetime NOT NULL,
|
|
20
|
+
updated_at datetime NOT NULL
|
|
21
|
+
)
|
|
22
|
+
SQL
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
after(:each) do
|
|
26
|
+
ActiveRecord::Base.connection.execute("DROP table IF EXISTS `users`")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class User < ActiveRecord::Base
|
|
30
|
+
include Flipper::Model::ActiveRecord
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class DelegatedUser < DelegateClass(User)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Admin < User
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "doesn't warn for to_ary" do
|
|
40
|
+
# looks like we should remove this but you are wrong, we have specs that
|
|
41
|
+
# fail if there are warnings and if this regresses it will print a warning
|
|
42
|
+
# so it is in fact testing something
|
|
43
|
+
user = User.create!(name: "Test")
|
|
44
|
+
Flipper.enabled?(:something, DelegatedUser.new(user))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "flipper_id" do
|
|
48
|
+
it "returns class name and id" do
|
|
49
|
+
expect(User.new(id: 1).flipper_id).to eq("User;1")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "uses base class name" do
|
|
53
|
+
expect(Admin.new(id: 2).flipper_id).to eq("User;2")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe "flipper_properties" do
|
|
58
|
+
subject { User.create!(name: "Test", age: 22, is_confirmed: true) }
|
|
59
|
+
|
|
60
|
+
it "includes all attributes" do
|
|
61
|
+
expect(subject.flipper_properties).to eq({
|
|
62
|
+
"type" => "User",
|
|
63
|
+
"id" => subject.id,
|
|
64
|
+
"name" => "Test",
|
|
65
|
+
"age" => 22,
|
|
66
|
+
"is_confirmed" => true,
|
|
67
|
+
"created_at" => subject.created_at,
|
|
68
|
+
"updated_at" => subject.updated_at
|
|
69
|
+
})
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|