flipper 0.28.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 +52 -10
- data/CLAUDE.md +74 -0
- data/Changelog.md +1 -526
- data/Gemfile +17 -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/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 +0 -14
- data/examples/expressions.rb +213 -0
- data/examples/mirroring.rb +59 -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 +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 +37 -19
- 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 +266 -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 +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 +53 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +47 -42
- 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/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 +28 -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 +10 -9
- 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 +44 -7
- 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/dual_write_spec.rb +2 -2
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +135 -74
- data/spec/flipper/adapters/instrumented_spec.rb +1 -1
- data/spec/flipper/adapters/memoizable_spec.rb +21 -21
- data/spec/flipper/adapters/memory_spec.rb +11 -2
- 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 +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 +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/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/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 +114 -16
- data/spec/flipper_spec.rb +87 -29
- data/spec/spec_helper.rb +17 -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 +179 -19
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
|
@@ -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,24 +8,31 @@ RSpec.describe Flipper do
|
|
|
8
8
|
let(:dev_group) { flipper.group(:devs) }
|
|
9
9
|
|
|
10
10
|
let(:admin_actor) do
|
|
11
|
-
double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false
|
|
11
|
+
double 'Non Flipper Thing', flipper_id: 1, admin?: true, dev?: false, flipper_properties: {"admin" => true, "dev" => false}
|
|
12
12
|
end
|
|
13
13
|
let(:dev_actor) do
|
|
14
|
-
double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true
|
|
14
|
+
double 'Non Flipper Thing', flipper_id: 10, admin?: false, dev?: true, flipper_properties: {"admin" => false, "dev" => true}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
let(:admin_truthy_actor) do
|
|
18
|
-
double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false
|
|
18
|
+
double 'Non Flipper Thing', flipper_id: 1, admin?: 'true-ish', dev?: false, flipper_properties: {"admin" => "true-ish", "dev" => false}
|
|
19
19
|
end
|
|
20
20
|
let(:admin_falsey_actor) do
|
|
21
|
-
double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false
|
|
21
|
+
double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false, flipper_properties: {"admin" => nil, "dev" => false}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:basic_plan_actor) do
|
|
25
|
+
double 'Non Flipper Thing', flipper_id: 1, flipper_properties: {"plan" => "basic"}
|
|
26
|
+
end
|
|
27
|
+
let(:premium_plan_actor) do
|
|
28
|
+
double 'Non Flipper Thing', flipper_id: 10, flipper_properties: {"plan" => "premium"}
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
let(:pitt) { Flipper::Actor.new(1) }
|
|
25
32
|
let(:clooney) { Flipper::Actor.new(10) }
|
|
26
33
|
|
|
27
|
-
let(:five_percent_of_actors) {
|
|
28
|
-
let(:five_percent_of_time) {
|
|
34
|
+
let(:five_percent_of_actors) { Flipper::Types::PercentageOfActors.new(5) }
|
|
35
|
+
let(:five_percent_of_time) { Flipper::Types::PercentageOfTime.new(5) }
|
|
29
36
|
|
|
30
37
|
before do
|
|
31
38
|
described_class.register(:admins, &:admin?)
|
|
@@ -69,11 +76,13 @@ RSpec.describe Flipper do
|
|
|
69
76
|
end
|
|
70
77
|
|
|
71
78
|
it 'enables feature for flipper actor in group' do
|
|
72
|
-
expect(feature.enabled?(
|
|
79
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(true)
|
|
80
|
+
expect(feature.enabled?(admin_actor)).to eq(true)
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
it 'does not enable for flipper actor not in group' do
|
|
76
|
-
expect(feature.enabled?(
|
|
84
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(false)
|
|
85
|
+
expect(feature.enabled?(dev_actor)).to eq(false)
|
|
77
86
|
end
|
|
78
87
|
|
|
79
88
|
it 'does not enable feature for all' do
|
|
@@ -256,11 +265,13 @@ RSpec.describe Flipper do
|
|
|
256
265
|
end
|
|
257
266
|
|
|
258
267
|
it 'disables feature for flipper actor in group' do
|
|
259
|
-
expect(feature.enabled?(
|
|
268
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(false)
|
|
269
|
+
expect(feature.enabled?(admin_actor)).to eq(false)
|
|
260
270
|
end
|
|
261
271
|
|
|
262
272
|
it 'does not disable feature for flipper actor in other groups' do
|
|
263
|
-
expect(feature.enabled?(
|
|
273
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(true)
|
|
274
|
+
expect(feature.enabled?(dev_actor)).to eq(true)
|
|
264
275
|
end
|
|
265
276
|
|
|
266
277
|
it 'adds feature to set of features' do
|
|
@@ -294,7 +305,7 @@ RSpec.describe Flipper do
|
|
|
294
305
|
|
|
295
306
|
context 'with a percentage of actors' do
|
|
296
307
|
before do
|
|
297
|
-
@result = feature.disable(
|
|
308
|
+
@result = feature.disable(Flipper::Types::PercentageOfActors.new(0))
|
|
298
309
|
end
|
|
299
310
|
|
|
300
311
|
it 'returns true' do
|
|
@@ -318,7 +329,7 @@ RSpec.describe Flipper do
|
|
|
318
329
|
context 'with a percentage of time' do
|
|
319
330
|
before do
|
|
320
331
|
@gate = feature.gate(:percentage_of_time)
|
|
321
|
-
@result = feature.disable(
|
|
332
|
+
@result = feature.disable(Flipper::Types::PercentageOfTime.new(0))
|
|
322
333
|
end
|
|
323
334
|
|
|
324
335
|
it 'returns true' do
|
|
@@ -373,12 +384,13 @@ RSpec.describe Flipper do
|
|
|
373
384
|
end
|
|
374
385
|
|
|
375
386
|
it 'returns true' do
|
|
376
|
-
expect(feature.enabled?(
|
|
387
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(admin_actor))).to eq(true)
|
|
377
388
|
expect(feature.enabled?(admin_actor)).to eq(true)
|
|
378
389
|
end
|
|
379
390
|
|
|
380
391
|
it 'returns true for truthy block values' do
|
|
381
|
-
expect(feature.enabled?(
|
|
392
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(admin_truthy_actor))).to eq(true)
|
|
393
|
+
expect(feature.enabled?(admin_truthy_actor)).to eq(true)
|
|
382
394
|
end
|
|
383
395
|
|
|
384
396
|
it 'returns true if any actor is in enabled group' do
|
|
@@ -388,12 +400,13 @@ RSpec.describe Flipper do
|
|
|
388
400
|
|
|
389
401
|
context 'for actor in disabled group' do
|
|
390
402
|
it 'returns false' do
|
|
391
|
-
expect(feature.enabled?(
|
|
403
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(dev_actor))).to eq(false)
|
|
392
404
|
expect(feature.enabled?(dev_actor)).to eq(false)
|
|
393
405
|
end
|
|
394
406
|
|
|
395
407
|
it 'returns false for falsey block values' do
|
|
396
|
-
expect(feature.enabled?(
|
|
408
|
+
expect(feature.enabled?(Flipper::Types::Actor.new(admin_falsey_actor))).to eq(false)
|
|
409
|
+
expect(feature.enabled?(admin_falsey_actor)).to eq(false)
|
|
397
410
|
end
|
|
398
411
|
end
|
|
399
412
|
|
|
@@ -549,4 +562,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
|