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
|
@@ -80,7 +80,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
80
80
|
context 'with preload: true' do
|
|
81
81
|
let(:app) do
|
|
82
82
|
# ensure scoped for builder block, annoying...
|
|
83
|
-
|
|
83
|
+
flipper
|
|
84
84
|
middleware = described_class
|
|
85
85
|
|
|
86
86
|
Rack::Builder.new do
|
|
@@ -141,7 +141,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
141
141
|
context 'with preload specific' do
|
|
142
142
|
let(:app) do
|
|
143
143
|
# ensure scoped for builder block, annoying...
|
|
144
|
-
|
|
144
|
+
flipper
|
|
145
145
|
middleware = described_class
|
|
146
146
|
|
|
147
147
|
Rack::Builder.new do
|
|
@@ -196,10 +196,77 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
196
196
|
end
|
|
197
197
|
end
|
|
198
198
|
|
|
199
|
+
context 'with preload block' do
|
|
200
|
+
let(:app) do
|
|
201
|
+
app = lambda do |_env|
|
|
202
|
+
flipper[:stats].enabled?
|
|
203
|
+
flipper[:stats].enabled?
|
|
204
|
+
flipper[:shiny].enabled?
|
|
205
|
+
flipper[:shiny].enabled?
|
|
206
|
+
[200, {}, []]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
described_class.new(app, preload: ->(request) {
|
|
210
|
+
case request.path
|
|
211
|
+
when "/true"
|
|
212
|
+
true
|
|
213
|
+
when "/specific"
|
|
214
|
+
[:stats]
|
|
215
|
+
else
|
|
216
|
+
false
|
|
217
|
+
end
|
|
218
|
+
})
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
include_examples 'flipper middleware'
|
|
222
|
+
|
|
223
|
+
it 'eagerly caches known features for duration of request if block returns true' do
|
|
224
|
+
flipper[:stats].enable
|
|
225
|
+
flipper[:shiny].enable
|
|
226
|
+
|
|
227
|
+
# clear the log of operations
|
|
228
|
+
adapter.reset
|
|
229
|
+
|
|
230
|
+
get '/true', {}, 'flipper' => flipper
|
|
231
|
+
|
|
232
|
+
expect(adapter.operations.size).to be(1)
|
|
233
|
+
expect(adapter.count(:get_all)).to be(1)
|
|
234
|
+
expect(adapter.count(:get)).to be(0)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it 'does not eagerly cache known features if block returns false' do
|
|
238
|
+
flipper[:stats].enable
|
|
239
|
+
flipper[:shiny].enable
|
|
240
|
+
|
|
241
|
+
# clear the log of operations
|
|
242
|
+
adapter.reset
|
|
243
|
+
|
|
244
|
+
get '/false', {}, 'flipper' => flipper
|
|
245
|
+
|
|
246
|
+
expect(adapter.operations.size).to be(2)
|
|
247
|
+
expect(adapter.count(:get_all)).to be(0)
|
|
248
|
+
expect(adapter.count(:get)).to be(2)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it 'eagerly caches specified features for duration of request if block returns array of specified features' do
|
|
252
|
+
flipper[:stats].enable
|
|
253
|
+
flipper[:shiny].enable
|
|
254
|
+
|
|
255
|
+
# clear the log of operations
|
|
256
|
+
adapter.reset
|
|
257
|
+
|
|
258
|
+
get '/specific', {}, 'flipper' => flipper
|
|
259
|
+
|
|
260
|
+
expect(adapter.operations.size).to be(2)
|
|
261
|
+
expect(adapter.count(:get_multi)).to be(1)
|
|
262
|
+
expect(adapter.count(:get)).to be(1)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
199
266
|
context 'with multiple instances' do
|
|
200
267
|
let(:app) do
|
|
201
268
|
# ensure scoped for builder block, annoying...
|
|
202
|
-
|
|
269
|
+
flipper
|
|
203
270
|
middleware = described_class
|
|
204
271
|
|
|
205
272
|
Rack::Builder.new do
|
|
@@ -218,7 +285,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
218
285
|
end
|
|
219
286
|
|
|
220
287
|
def get(uri, params = {}, env = {}, &block)
|
|
221
|
-
|
|
288
|
+
capture_output { super(uri, params, env, &block) }
|
|
222
289
|
end
|
|
223
290
|
|
|
224
291
|
include_examples 'flipper middleware'
|
|
@@ -249,7 +316,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
249
316
|
context 'with flipper setup in env' do
|
|
250
317
|
let(:app) do
|
|
251
318
|
# ensure scoped for builder block, annoying...
|
|
252
|
-
|
|
319
|
+
flipper
|
|
253
320
|
middleware = described_class
|
|
254
321
|
|
|
255
322
|
Rack::Builder.new do
|
|
@@ -391,9 +458,8 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
391
458
|
logged_memory = Flipper::Adapters::OperationLogger.new(memory)
|
|
392
459
|
cache = ActiveSupport::Cache::MemoryStore.new
|
|
393
460
|
cache.clear
|
|
394
|
-
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache
|
|
461
|
+
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache)
|
|
395
462
|
logged_cached = Flipper::Adapters::OperationLogger.new(cached)
|
|
396
|
-
memo = {}
|
|
397
463
|
flipper = Flipper.new(logged_cached)
|
|
398
464
|
flipper[:stats].enable
|
|
399
465
|
flipper[:shiny].enable
|
|
@@ -404,15 +470,18 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
404
470
|
|
|
405
471
|
get '/', {}, 'flipper' => flipper
|
|
406
472
|
expect(logged_cached.count(:get_all)).to be(1)
|
|
407
|
-
expect(logged_memory.count(:
|
|
473
|
+
expect(logged_memory.count(:features)).to be(1)
|
|
474
|
+
expect(logged_memory.count(:get_multi)).to be(1)
|
|
408
475
|
|
|
409
476
|
get '/', {}, 'flipper' => flipper
|
|
410
477
|
expect(logged_cached.count(:get_all)).to be(2)
|
|
411
|
-
expect(logged_memory.count(:
|
|
478
|
+
expect(logged_memory.count(:features)).to be(1)
|
|
479
|
+
expect(logged_memory.count(:get_multi)).to be(1)
|
|
412
480
|
|
|
413
481
|
get '/', {}, 'flipper' => flipper
|
|
414
482
|
expect(logged_cached.count(:get_all)).to be(3)
|
|
415
|
-
expect(logged_memory.count(:
|
|
483
|
+
expect(logged_memory.count(:features)).to be(1)
|
|
484
|
+
expect(logged_memory.count(:get_multi)).to be(1)
|
|
416
485
|
end
|
|
417
486
|
end
|
|
418
487
|
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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'flipper/serializers/gzip'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Flipper::Serializers::Gzip do
|
|
4
|
+
it "serializes and deserializes" do
|
|
5
|
+
serialized = described_class.serialize("my data")
|
|
6
|
+
expect(described_class.deserialize(serialized)).to eq("my data")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "doesn't fail with nil" do
|
|
10
|
+
expect(described_class.serialize(nil)).to be(nil)
|
|
11
|
+
expect(described_class.deserialize(nil)).to be(nil)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'flipper/serializers/json'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Flipper::Serializers::Json do
|
|
4
|
+
it "serializes and deserializes" do
|
|
5
|
+
serialized = described_class.serialize("my data")
|
|
6
|
+
expect(described_class.deserialize(serialized)).to eq("my data")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "doesn't fail with nil" do
|
|
10
|
+
expect(described_class.serialize(nil)).to be(nil)
|
|
11
|
+
expect(described_class.deserialize(nil)).to be(nil)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -56,7 +56,7 @@ RSpec.describe Flipper::Typecast do
|
|
|
56
56
|
nil => 0,
|
|
57
57
|
'' => 0,
|
|
58
58
|
0 => 0,
|
|
59
|
-
0.0 => 0,
|
|
59
|
+
0.0 => 0.0,
|
|
60
60
|
1 => 1,
|
|
61
61
|
1.1 => 1.1,
|
|
62
62
|
'0.01' => 0.01,
|
|
@@ -65,9 +65,9 @@ RSpec.describe Flipper::Typecast do
|
|
|
65
65
|
'99' => 99,
|
|
66
66
|
'99.9' => 99.9,
|
|
67
67
|
}.each do |value, expected|
|
|
68
|
-
context "#
|
|
68
|
+
context "#to_number for #{value.inspect}" do
|
|
69
69
|
it "returns #{expected}" do
|
|
70
|
-
expect(described_class.
|
|
70
|
+
expect(described_class.to_number(value)).to be(expected)
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
end
|
|
@@ -99,14 +99,14 @@ RSpec.describe Flipper::Typecast do
|
|
|
99
99
|
|
|
100
100
|
it 'raises argument error for bad integer percentage' do
|
|
101
101
|
expect do
|
|
102
|
-
described_class.
|
|
103
|
-
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a
|
|
102
|
+
described_class.to_number(['asdf'])
|
|
103
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a number))
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
it 'raises argument error for bad float percentage' do
|
|
107
107
|
expect do
|
|
108
|
-
described_class.
|
|
109
|
-
end.to raise_error(ArgumentError, %(["asdf.0"] cannot be converted to a
|
|
108
|
+
described_class.to_number(['asdf.0'])
|
|
109
|
+
end.to raise_error(ArgumentError, %(["asdf.0"] cannot be converted to a number))
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
it 'raises argument error for set value that cannot be converted to a set' do
|
|
@@ -127,6 +127,30 @@ RSpec.describe Flipper::Typecast do
|
|
|
127
127
|
expect(result["search"]).not_to be(hash["search"])
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
it "converts does not convert expressions" do
|
|
131
|
+
hash = {
|
|
132
|
+
"search" => {
|
|
133
|
+
boolean: nil,
|
|
134
|
+
expression: {"Equal"=>[{"Property"=>["plan"]}, "basic"]},
|
|
135
|
+
groups: ['a', 'b'],
|
|
136
|
+
actors: ['User;1'],
|
|
137
|
+
percentage_of_actors: nil,
|
|
138
|
+
percentage_of_time: nil,
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
result = described_class.features_hash(hash)
|
|
142
|
+
expect(result).to eq({
|
|
143
|
+
"search" => {
|
|
144
|
+
boolean: nil,
|
|
145
|
+
expression: {"Equal"=>[{"Property"=>["plan"]}, "basic"]},
|
|
146
|
+
groups: Set['a', 'b'],
|
|
147
|
+
actors: Set['User;1'],
|
|
148
|
+
percentage_of_actors: nil,
|
|
149
|
+
percentage_of_time: nil,
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
end
|
|
153
|
+
|
|
130
154
|
it "converts gate value arrays to sets" do
|
|
131
155
|
hash = {
|
|
132
156
|
"search" => {
|
|
@@ -193,4 +217,16 @@ RSpec.describe Flipper::Typecast do
|
|
|
193
217
|
})
|
|
194
218
|
end
|
|
195
219
|
end
|
|
220
|
+
|
|
221
|
+
it "converts to and from json" do
|
|
222
|
+
source = {"foo" => "bar"}
|
|
223
|
+
output = described_class.to_json(source)
|
|
224
|
+
expect(described_class.from_json(output)).to eq(source)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it "converts to and from gzip" do
|
|
228
|
+
source = "foo bar"
|
|
229
|
+
output = described_class.to_gzip(source)
|
|
230
|
+
expect(described_class.from_gzip(output)).to eq(source)
|
|
231
|
+
end
|
|
196
232
|
end
|
|
@@ -11,12 +11,19 @@ RSpec.describe Flipper::Types::Actor do
|
|
|
11
11
|
attr_reader :flipper_id
|
|
12
12
|
|
|
13
13
|
def initialize(flipper_id)
|
|
14
|
-
@flipper_id = flipper_id
|
|
14
|
+
@flipper_id = flipper_id.to_s
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def admin?
|
|
18
18
|
true
|
|
19
19
|
end
|
|
20
|
+
|
|
21
|
+
def flipper_properties
|
|
22
|
+
{
|
|
23
|
+
"flipper_id" => flipper_id,
|
|
24
|
+
"admin" => admin?,
|
|
25
|
+
}
|
|
26
|
+
end
|
|
20
27
|
end
|
|
21
28
|
end
|
|
22
29
|
|
|
@@ -87,6 +94,15 @@ RSpec.describe Flipper::Types::Actor do
|
|
|
87
94
|
expect(actor.admin?).to eq(true)
|
|
88
95
|
end
|
|
89
96
|
|
|
97
|
+
it 'proxies flipper_properties to actor' do
|
|
98
|
+
actor = actor_class.new(10)
|
|
99
|
+
actor = described_class.new(actor)
|
|
100
|
+
expect(actor.flipper_properties).to eq({
|
|
101
|
+
"flipper_id" => "10",
|
|
102
|
+
"admin" => true,
|
|
103
|
+
})
|
|
104
|
+
end
|
|
105
|
+
|
|
90
106
|
it 'exposes actor' do
|
|
91
107
|
actor = actor_class.new(10)
|
|
92
108
|
actor_type_instance = described_class.new(actor)
|
|
@@ -104,6 +120,7 @@ RSpec.describe Flipper::Types::Actor do
|
|
|
104
120
|
actor = actor_class.new(10)
|
|
105
121
|
actor_type_instance = described_class.new(actor)
|
|
106
122
|
expect(actor_type_instance.respond_to?(:admin?)).to eq(true)
|
|
123
|
+
expect(actor_type_instance.respond_to?(:flipper_properties)).to eq(true)
|
|
107
124
|
end
|
|
108
125
|
|
|
109
126
|
it 'returns false if does not respond to method and actor does not respond to method' do
|
|
@@ -8,17 +8,24 @@ RSpec.describe Flipper do
|
|
|
8
8
|
let(:dev_group) { flipper.group(:devs) }
|
|
9
9
|
|
|
10
10
|
let(:admin_actor) do
|
|
11
|
-
double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false
|
|
11
|
+
double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false, flipper_properties: {"admin" => true, "dev" => false}
|
|
12
12
|
end
|
|
13
13
|
let(:dev_actor) do
|
|
14
|
-
double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true
|
|
14
|
+
double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true, flipper_properties: {"admin" => false, "dev" => true}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
let(:admin_truthy_actor) do
|
|
18
|
-
double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false
|
|
18
|
+
double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false, flipper_properties: {"admin" => "true-ish", "dev" => false}
|
|
19
19
|
end
|
|
20
20
|
let(:admin_falsey_actor) do
|
|
21
|
-
double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false
|
|
21
|
+
double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false, flipper_properties: {"admin" => nil, "dev" => false}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:basic_plan_actor) do
|
|
25
|
+
double 'Non Flipper Thing', flipper_id: 1, flipper_properties: {"plan" => "basic"}
|
|
26
|
+
end
|
|
27
|
+
let(:premium_plan_actor) do
|
|
28
|
+
double 'Non Flipper Thing', flipper_id: 10, flipper_properties: {"plan" => "premium"}
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
let(:pitt) { Flipper::Actor.new(1) }
|
|
@@ -70,10 +77,12 @@ RSpec.describe Flipper do
|
|
|
70
77
|
|
|
71
78
|
it 'enables feature for flipper actor in group' do
|
|
72
79
|
expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(true)
|
|
80
|
+
expect(feature.enabled?(admin_actor)).to eq(true)
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
it 'does not enable for flipper actor not in group' do
|
|
76
84
|
expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(false)
|
|
85
|
+
expect(feature.enabled?(dev_actor)).to eq(false)
|
|
77
86
|
end
|
|
78
87
|
|
|
79
88
|
it 'does not enable feature for all' do
|
|
@@ -257,10 +266,12 @@ RSpec.describe Flipper do
|
|
|
257
266
|
|
|
258
267
|
it 'disables feature for flipper actor in group' do
|
|
259
268
|
expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(false)
|
|
269
|
+
expect(feature.enabled?(admin_actor)).to eq(false)
|
|
260
270
|
end
|
|
261
271
|
|
|
262
272
|
it 'does not disable feature for flipper actor in other groups' do
|
|
263
273
|
expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(true)
|
|
274
|
+
expect(feature.enabled?(dev_actor)).to eq(true)
|
|
264
275
|
end
|
|
265
276
|
|
|
266
277
|
it 'adds feature to set of features' do
|
|
@@ -379,6 +390,7 @@ RSpec.describe Flipper do
|
|
|
379
390
|
|
|
380
391
|
it 'returns true for truthy block values' do
|
|
381
392
|
expect(feature.enabled?(Flipper::Types::Actor.new(admin_truthy_actor))).to eq(true)
|
|
393
|
+
expect(feature.enabled?(admin_truthy_actor)).to eq(true)
|
|
382
394
|
end
|
|
383
395
|
|
|
384
396
|
it 'returns true if any actor is in enabled group' do
|
|
@@ -394,6 +406,7 @@ RSpec.describe Flipper do
|
|
|
394
406
|
|
|
395
407
|
it 'returns false for falsey block values' do
|
|
396
408
|
expect(feature.enabled?(Flipper::Types::Actor.new(admin_falsey_actor))).to eq(false)
|
|
409
|
+
expect(feature.enabled?(admin_falsey_actor)).to eq(false)
|
|
397
410
|
end
|
|
398
411
|
end
|
|
399
412
|
|
|
@@ -549,4 +562,89 @@ RSpec.describe Flipper do
|
|
|
549
562
|
expect(feature.enabled?(dev_actor)).to eq(false)
|
|
550
563
|
end
|
|
551
564
|
end
|
|
565
|
+
|
|
566
|
+
context "for expression" do
|
|
567
|
+
it "works" do
|
|
568
|
+
feature.enable Flipper.property(:plan).eq("basic")
|
|
569
|
+
|
|
570
|
+
expect(feature.enabled?).to be(false)
|
|
571
|
+
expect(feature.enabled?(basic_plan_actor)).to be(true)
|
|
572
|
+
expect(feature.enabled?(premium_plan_actor)).to be(false)
|
|
573
|
+
expect(feature.enabled?(admin_actor)).to be(false)
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
it "works for true expression with no actor" do
|
|
577
|
+
feature.enable Flipper.boolean(true)
|
|
578
|
+
expect(feature.enabled?).to be(true)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
it "works for multiple actors" do
|
|
582
|
+
feature.enable Flipper.property(:plan).eq("basic")
|
|
583
|
+
|
|
584
|
+
expect(feature.enabled?(basic_plan_actor, premium_plan_actor)).to be(true)
|
|
585
|
+
expect(feature.enabled?(premium_plan_actor, basic_plan_actor)).to be(true)
|
|
586
|
+
expect(feature.enabled?(premium_plan_actor, admin_actor)).to be(false)
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
context "for Any" do
|
|
591
|
+
it "works" do
|
|
592
|
+
expression = Flipper.any(
|
|
593
|
+
Flipper.property(:plan).eq("basic"),
|
|
594
|
+
Flipper.property(:plan).eq("plus"),
|
|
595
|
+
)
|
|
596
|
+
feature.enable expression
|
|
597
|
+
|
|
598
|
+
expect(feature.enabled?(basic_plan_actor)).to be(true)
|
|
599
|
+
expect(feature.enabled?(premium_plan_actor)).to be(false)
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
context "for All" do
|
|
604
|
+
it "works" do
|
|
605
|
+
true_actor = Flipper::Actor.new("User;1", {
|
|
606
|
+
"plan" => "basic",
|
|
607
|
+
"age" => 21,
|
|
608
|
+
})
|
|
609
|
+
false_actor = Flipper::Actor.new("User;1", {
|
|
610
|
+
"plan" => "basic",
|
|
611
|
+
"age" => 20,
|
|
612
|
+
})
|
|
613
|
+
expression = Flipper.all(
|
|
614
|
+
Flipper.property(:plan).eq("basic"),
|
|
615
|
+
Flipper.property(:age).eq(21)
|
|
616
|
+
)
|
|
617
|
+
feature.enable expression
|
|
618
|
+
|
|
619
|
+
expect(feature.enabled?(true_actor)).to be(true)
|
|
620
|
+
expect(feature.enabled?(false_actor)).to be(false)
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
it "works when nested" do
|
|
624
|
+
admin_actor = Flipper::Actor.new("User;1", {
|
|
625
|
+
"admin" => true,
|
|
626
|
+
})
|
|
627
|
+
true_actor = Flipper::Actor.new("User;1", {
|
|
628
|
+
"plan" => "basic",
|
|
629
|
+
"age" => 21,
|
|
630
|
+
})
|
|
631
|
+
false_actor = Flipper::Actor.new("User;1", {
|
|
632
|
+
"plan" => "basic",
|
|
633
|
+
"age" => 20,
|
|
634
|
+
})
|
|
635
|
+
expression = Flipper.any(
|
|
636
|
+
Flipper.property(:admin).eq(true),
|
|
637
|
+
Flipper.all(
|
|
638
|
+
Flipper.property(:plan).eq("basic"),
|
|
639
|
+
Flipper.property(:age).eq(21)
|
|
640
|
+
)
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
feature.enable expression
|
|
644
|
+
|
|
645
|
+
expect(feature.enabled?(admin_actor)).to be(true)
|
|
646
|
+
expect(feature.enabled?(true_actor)).to be(true)
|
|
647
|
+
expect(feature.enabled?(false_actor)).to be(false)
|
|
648
|
+
end
|
|
649
|
+
end
|
|
552
650
|
end
|
data/spec/flipper_spec.rb
CHANGED
|
@@ -64,7 +64,12 @@ RSpec.describe Flipper do
|
|
|
64
64
|
|
|
65
65
|
describe "delegation to instance" do
|
|
66
66
|
let(:group) { Flipper::Types::Group.new(:admins) }
|
|
67
|
-
let(:actor) {
|
|
67
|
+
let(:actor) {
|
|
68
|
+
Flipper::Actor.new("1", {
|
|
69
|
+
"plan" => "basic",
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
let(:expression) { Flipper.property(:plan).eq("basic") }
|
|
68
73
|
|
|
69
74
|
before do
|
|
70
75
|
described_class.configure do |config|
|
|
@@ -88,6 +93,37 @@ RSpec.describe Flipper do
|
|
|
88
93
|
expect(described_class.instance.enabled?(:search)).to be(false)
|
|
89
94
|
end
|
|
90
95
|
|
|
96
|
+
it 'delegates expression to instance' do
|
|
97
|
+
expect(described_class.expression(:search)).to be(nil)
|
|
98
|
+
|
|
99
|
+
expression = Flipper.property(:plan).eq("basic")
|
|
100
|
+
Flipper.instance.enable_expression :search, expression
|
|
101
|
+
|
|
102
|
+
expect(described_class.expression(:search)).to eq(expression)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'delegates enable_expression to instance' do
|
|
106
|
+
described_class.enable_expression(:search, expression)
|
|
107
|
+
expect(described_class.instance.enabled?(:search, actor)).to be(true)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'delegates disable_expression to instance' do
|
|
111
|
+
described_class.disable_expression(:search)
|
|
112
|
+
expect(described_class.instance.enabled?(:search, actor)).to be(false)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'delegates add_expression to instance' do
|
|
116
|
+
described_class.add_expression(:search, expression)
|
|
117
|
+
expect(described_class.instance.enabled?(:search, actor)).to be(true)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'delegates remove_expression to instance' do
|
|
121
|
+
described_class.enable_expression(:search, Flipper.any(expression))
|
|
122
|
+
expect(described_class.instance.enabled?(:search, actor)).to be(true)
|
|
123
|
+
described_class.remove_expression(:search, expression)
|
|
124
|
+
expect(described_class.instance.enabled?(:search, actor)).to be(false)
|
|
125
|
+
end
|
|
126
|
+
|
|
91
127
|
it 'delegates enable_actor to instance' do
|
|
92
128
|
described_class.enable_actor(:search, actor)
|
|
93
129
|
expect(described_class.instance.enabled?(:search, actor)).to be(true)
|
|
@@ -192,6 +228,10 @@ RSpec.describe Flipper do
|
|
|
192
228
|
expect(described_class.memoizing?).to eq(described_class.adapter.memoizing?)
|
|
193
229
|
end
|
|
194
230
|
|
|
231
|
+
it 'delegates read_only? to instance' do
|
|
232
|
+
expect(described_class.read_only?).to eq(described_class.adapter.read_only?)
|
|
233
|
+
end
|
|
234
|
+
|
|
195
235
|
it 'delegates sync stuff to instance and does nothing' do
|
|
196
236
|
expect(described_class.sync).to be(nil)
|
|
197
237
|
expect(described_class.sync_secret).to be(nil)
|
|
@@ -201,7 +241,7 @@ RSpec.describe Flipper do
|
|
|
201
241
|
stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
|
|
202
242
|
with({
|
|
203
243
|
headers: {
|
|
204
|
-
'
|
|
244
|
+
'flipper-cloud-token'=>'asdf',
|
|
205
245
|
},
|
|
206
246
|
}).to_return(status: 200, body: '{"features": {}}', headers: {})
|
|
207
247
|
cloud_configuration = Flipper::Cloud::Configuration.new({
|
|
@@ -273,7 +313,7 @@ RSpec.describe Flipper do
|
|
|
273
313
|
|
|
274
314
|
describe '.group_exists' do
|
|
275
315
|
it 'returns true if the group is already created' do
|
|
276
|
-
|
|
316
|
+
described_class.register('admins', &:admin?)
|
|
277
317
|
expect(described_class.group_exists?(:admins)).to eq(true)
|
|
278
318
|
end
|
|
279
319
|
|
|
@@ -326,4 +366,52 @@ RSpec.describe Flipper do
|
|
|
326
366
|
expect(described_class.instance_variable_get('@groups_registry')).to eq(registry)
|
|
327
367
|
end
|
|
328
368
|
end
|
|
369
|
+
|
|
370
|
+
describe ".constant" do
|
|
371
|
+
it "returns Flipper::Expression::Constant instance" do
|
|
372
|
+
expect(described_class.constant(false)).to eq(Flipper::Expression::Constant.new(false))
|
|
373
|
+
expect(described_class.constant("string")).to eq(Flipper::Expression::Constant.new("string"))
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
describe ".property" do
|
|
378
|
+
it "returns Flipper::Expressions::Property expression" do
|
|
379
|
+
expect(Flipper.property("name")).to eq(Flipper::Expression.build(Property: "name"))
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
describe ".boolean" do
|
|
384
|
+
it "returns Flipper::Expressions::Boolean expression" do
|
|
385
|
+
expect(described_class.boolean(true)).to eq(Flipper::Expression.build(Boolean: true))
|
|
386
|
+
expect(described_class.boolean(false)).to eq(Flipper::Expression.build(Boolean: false))
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
describe ".random" do
|
|
391
|
+
it "returns Flipper::Expressions::Random expression" do
|
|
392
|
+
expect(Flipper.random(100)).to eq(Flipper::Expression.build(Random: 100))
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
describe ".any" do
|
|
397
|
+
let(:age_expression) { Flipper.property(:age).gte(21) }
|
|
398
|
+
let(:plan_expression) { Flipper.property(:plan).eq("basic") }
|
|
399
|
+
|
|
400
|
+
it "returns Flipper::Expressions::Any instance" do
|
|
401
|
+
expect(Flipper.any(age_expression, plan_expression)).to eq(
|
|
402
|
+
Flipper::Expression.build({Any: [age_expression, plan_expression]})
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
describe ".all" do
|
|
408
|
+
let(:age_expression) { Flipper.property(:age).gte(21) }
|
|
409
|
+
let(:plan_expression) { Flipper.property(:plan).eq("basic") }
|
|
410
|
+
|
|
411
|
+
it "returns Flipper::Expressions::All instance" do
|
|
412
|
+
expect(Flipper.all(age_expression, plan_expression)).to eq(
|
|
413
|
+
Flipper::Expression.build({All: [age_expression, plan_expression]})
|
|
414
|
+
)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
329
417
|
end
|