flipper 1.2.1 → 1.3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -1
- data/.github/workflows/examples.yml +1 -1
- data/Gemfile +0 -1
- data/README.md +1 -0
- data/lib/flipper/adapters/cache_base.rb +143 -0
- data/lib/flipper/adapters/operation_logger.rb +18 -88
- data/lib/flipper/adapters/read_only.rb +6 -39
- data/lib/flipper/adapters/strict.rb +5 -10
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +38 -15
- data/lib/flipper/cloud/configuration.rb +2 -3
- data/lib/flipper/cloud/telemetry/instrumenter.rb +4 -8
- data/lib/flipper/cloud/telemetry.rb +10 -2
- data/lib/flipper/poller.rb +6 -5
- data/lib/flipper/serializers/gzip.rb +3 -5
- data/lib/flipper/serializers/json.rb +3 -5
- data/lib/flipper/spec/shared_adapter_specs.rb +17 -16
- data/lib/flipper/test/shared_adapter_test.rb +17 -17
- data/lib/flipper/test_help.rb +15 -10
- data/lib/flipper/typecast.rb +3 -3
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +1 -0
- data/package-lock.json +41 -0
- data/package.json +10 -0
- data/spec/flipper/adapters/http_spec.rb +11 -2
- data/spec/flipper/cli_spec.rb +23 -23
- data/spec/flipper/cloud/configuration_spec.rb +29 -37
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +8 -9
- data/spec/flipper/cloud/telemetry_spec.rb +52 -0
- data/spec/flipper/cloud_spec.rb +6 -5
- data/spec/flipper/engine_spec.rb +39 -49
- data/spec/flipper/middleware/memoizer_spec.rb +7 -4
- data/spec/support/fail_on_output.rb +8 -0
- data/spec/support/spec_helpers.rb +2 -1
- data/test_rails/system/test_help_test.rb +8 -3
- metadata +9 -5
- data/spec/support/climate_control.rb +0 -7
@@ -2,6 +2,12 @@ require 'flipper/cloud/telemetry'
|
|
2
2
|
require 'flipper/cloud/configuration'
|
3
3
|
|
4
4
|
RSpec.describe Flipper::Cloud::Telemetry do
|
5
|
+
before do
|
6
|
+
# Stub polling for features.
|
7
|
+
stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
|
8
|
+
to_return(status: 200, body: "{}")
|
9
|
+
end
|
10
|
+
|
5
11
|
it "phones home and does not update telemetry interval if missing" do
|
6
12
|
stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
|
7
13
|
to_return(status: 200, body: "{}")
|
@@ -42,6 +48,52 @@ RSpec.describe Flipper::Cloud::Telemetry do
|
|
42
48
|
expect(stub).to have_been_requested
|
43
49
|
end
|
44
50
|
|
51
|
+
it "phones home and requests shutdown if telemetry-shutdown header is true" do
|
52
|
+
stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
|
53
|
+
to_return(status: 404, body: "{}", headers: {"telemetry-shutdown" => "true"})
|
54
|
+
|
55
|
+
output = StringIO.new
|
56
|
+
cloud_configuration = Flipper::Cloud::Configuration.new(
|
57
|
+
token: "test",
|
58
|
+
logger: Logger.new(output),
|
59
|
+
logging_enabled: true,
|
60
|
+
)
|
61
|
+
|
62
|
+
# Record some telemetry and stop the threads so we submit a response.
|
63
|
+
telemetry = described_class.new(cloud_configuration)
|
64
|
+
telemetry.record(Flipper::Feature::InstrumentationName, {
|
65
|
+
operation: :enabled?,
|
66
|
+
feature_name: :foo,
|
67
|
+
result: true,
|
68
|
+
})
|
69
|
+
telemetry.stop
|
70
|
+
expect(stub).to have_been_requested
|
71
|
+
expect(output.string).to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "phones home and does not shutdown if telemetry shutdown header is missing" do
|
75
|
+
stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
|
76
|
+
to_return(status: 404, body: "{}", headers: {})
|
77
|
+
|
78
|
+
output = StringIO.new
|
79
|
+
cloud_configuration = Flipper::Cloud::Configuration.new(
|
80
|
+
token: "test",
|
81
|
+
logger: Logger.new(output),
|
82
|
+
logging_enabled: true,
|
83
|
+
)
|
84
|
+
|
85
|
+
# Record some telemetry and stop the threads so we submit a response.
|
86
|
+
telemetry = described_class.new(cloud_configuration)
|
87
|
+
telemetry.record(Flipper::Feature::InstrumentationName, {
|
88
|
+
operation: :enabled?,
|
89
|
+
feature_name: :foo,
|
90
|
+
result: true,
|
91
|
+
})
|
92
|
+
telemetry.stop
|
93
|
+
expect(stub).to have_been_requested
|
94
|
+
expect(output.string).not_to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
|
95
|
+
end
|
96
|
+
|
45
97
|
it "can update telemetry interval from error" do
|
46
98
|
stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
|
47
99
|
to_return(status: 500, body: "{}", headers: {"telemetry-interval" => "120"})
|
data/spec/flipper/cloud_spec.rb
CHANGED
@@ -36,7 +36,8 @@ RSpec.describe Flipper::Cloud do
|
|
36
36
|
expect(client.uri.host).to eq('www.flippercloud.io')
|
37
37
|
expect(client.uri.path).to eq('/adapter')
|
38
38
|
expect(client.headers["flipper-cloud-token"]).to eq(token)
|
39
|
-
expect(@instance.instrumenter).to
|
39
|
+
expect(@instance.instrumenter).to be_a(Flipper::Cloud::Telemetry::Instrumenter)
|
40
|
+
expect(@instance.instrumenter.instrumenter).to be(Flipper::Instrumenters::Noop)
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -55,15 +56,15 @@ RSpec.describe Flipper::Cloud do
|
|
55
56
|
end
|
56
57
|
|
57
58
|
it 'can initialize with no token explicitly provided' do
|
58
|
-
|
59
|
-
|
60
|
-
end
|
59
|
+
ENV['FLIPPER_CLOUD_TOKEN'] = 'asdf'
|
60
|
+
expect(described_class.new).to be_instance_of(Flipper::Cloud::DSL)
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'can set instrumenter' do
|
64
64
|
instrumenter = Flipper::Instrumenters::Memory.new
|
65
65
|
instance = described_class.new(token: 'asdf', instrumenter: instrumenter)
|
66
|
-
expect(instance.instrumenter).to
|
66
|
+
expect(instance.instrumenter).to be_a(Flipper::Cloud::Telemetry::Instrumenter)
|
67
|
+
expect(instance.instrumenter.instrumenter).to be(instrumenter)
|
67
68
|
end
|
68
69
|
|
69
70
|
it 'allows wrapping adapter with another adapter like the instrumenter' do
|
data/spec/flipper/engine_spec.rb
CHANGED
@@ -4,8 +4,10 @@ require 'flipper/engine'
|
|
4
4
|
RSpec.describe Flipper::Engine do
|
5
5
|
let(:application) do
|
6
6
|
Class.new(Rails::Application) do
|
7
|
+
config.load_defaults Rails::VERSION::STRING.to_f
|
7
8
|
config.eager_load = false
|
8
9
|
config.logger = ActiveSupport::Logger.new($stdout)
|
10
|
+
config.active_support.remove_deprecated_time_with_zone_name = false
|
9
11
|
end.instance
|
10
12
|
end
|
11
13
|
|
@@ -33,28 +35,25 @@ RSpec.describe Flipper::Engine do
|
|
33
35
|
let(:adapter) { Flipper.adapter.adapter }
|
34
36
|
|
35
37
|
it 'can set strict=true from ENV' do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
38
|
+
ENV['FLIPPER_STRICT'] = 'true'
|
39
|
+
subject
|
40
|
+
expect(config.strict).to eq(:raise)
|
41
|
+
expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
|
41
42
|
end
|
42
43
|
|
43
44
|
it 'can set strict=warn from ENV' do
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
45
|
+
ENV['FLIPPER_STRICT'] = 'warn'
|
46
|
+
subject
|
47
|
+
expect(config.strict).to eq(:warn)
|
48
|
+
expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
|
49
|
+
expect(adapter.handler).to be(:warn)
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'can set strict=false from ENV' do
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
53
|
+
ENV['FLIPPER_STRICT'] = 'false'
|
54
|
+
subject
|
55
|
+
expect(config.strict).to eq(false)
|
56
|
+
expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
|
58
57
|
end
|
59
58
|
|
60
59
|
[true, :raise, :warn].each do |value|
|
@@ -104,39 +103,34 @@ RSpec.describe Flipper::Engine do
|
|
104
103
|
it_behaves_like 'config.strict'
|
105
104
|
|
106
105
|
it 'can set env_key from ENV' do
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
106
|
+
ENV['FLIPPER_ENV_KEY'] = 'flopper'
|
107
|
+
subject
|
108
|
+
expect(config.env_key).to eq('flopper')
|
111
109
|
end
|
112
110
|
|
113
111
|
it 'can set memoize from ENV' do
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
112
|
+
ENV['FLIPPER_MEMOIZE'] = 'false'
|
113
|
+
subject
|
114
|
+
expect(config.memoize).to eq(false)
|
118
115
|
end
|
119
116
|
|
120
117
|
it 'can set preload from ENV' do
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
118
|
+
ENV['FLIPPER_PRELOAD'] = 'false'
|
119
|
+
subject
|
120
|
+
expect(config.preload).to eq(false)
|
125
121
|
end
|
126
122
|
|
127
123
|
it 'can set instrumenter from ENV' do
|
128
124
|
stub_const('My::Cool::Instrumenter', Class.new)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
end
|
125
|
+
ENV['FLIPPER_INSTRUMENTER'] = 'My::Cool::Instrumenter'
|
126
|
+
subject
|
127
|
+
expect(config.instrumenter).to eq(My::Cool::Instrumenter)
|
133
128
|
end
|
134
129
|
|
135
130
|
it 'can set log from ENV' do
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
end
|
131
|
+
ENV['FLIPPER_LOG'] = 'false'
|
132
|
+
subject
|
133
|
+
expect(config.log).to eq(false)
|
140
134
|
end
|
141
135
|
|
142
136
|
it 'sets defaults' do
|
@@ -220,10 +214,8 @@ RSpec.describe Flipper::Engine do
|
|
220
214
|
end
|
221
215
|
|
222
216
|
context 'with cloud' do
|
223
|
-
|
224
|
-
|
225
|
-
example.run
|
226
|
-
end
|
217
|
+
before do
|
218
|
+
ENV["FLIPPER_CLOUD_TOKEN"] = "test-token"
|
227
219
|
end
|
228
220
|
|
229
221
|
# App for Rack::Test
|
@@ -244,14 +236,13 @@ RSpec.describe Flipper::Engine do
|
|
244
236
|
application.initialize!
|
245
237
|
|
246
238
|
expect(Flipper.instance).to be_a(Flipper::Cloud::DSL)
|
247
|
-
expect(Flipper.instance.instrumenter).to
|
239
|
+
expect(Flipper.instance.instrumenter).to be_a(Flipper::Cloud::Telemetry::Instrumenter)
|
240
|
+
expect(Flipper.instance.instrumenter.instrumenter).to be(ActiveSupport::Notifications)
|
248
241
|
end
|
249
242
|
|
250
243
|
context "with CLOUD_SYNC_SECRET" do
|
251
|
-
|
252
|
-
|
253
|
-
example.run
|
254
|
-
end
|
244
|
+
before do
|
245
|
+
ENV["FLIPPER_CLOUD_SYNC_SECRET"] = "test-secret"
|
255
246
|
end
|
256
247
|
|
257
248
|
let(:request_body) do
|
@@ -296,10 +287,9 @@ RSpec.describe Flipper::Engine do
|
|
296
287
|
|
297
288
|
context "without FLIPPER_CLOUD_TOKEN" do
|
298
289
|
it "gracefully skips configuring webhook app" do
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
end
|
290
|
+
ENV["FLIPPER_CLOUD_TOKEN"] = nil
|
291
|
+
application.initialize!
|
292
|
+
expect(Flipper.instance).to be_a(Flipper::DSL)
|
303
293
|
|
304
294
|
post "/_flipper"
|
305
295
|
expect(last_response.status).to eq(404)
|
@@ -458,7 +458,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
458
458
|
logged_memory = Flipper::Adapters::OperationLogger.new(memory)
|
459
459
|
cache = ActiveSupport::Cache::MemoryStore.new
|
460
460
|
cache.clear
|
461
|
-
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache
|
461
|
+
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache)
|
462
462
|
logged_cached = Flipper::Adapters::OperationLogger.new(cached)
|
463
463
|
memo = {}
|
464
464
|
flipper = Flipper.new(logged_cached)
|
@@ -471,15 +471,18 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
471
471
|
|
472
472
|
get '/', {}, 'flipper' => flipper
|
473
473
|
expect(logged_cached.count(:get_all)).to be(1)
|
474
|
-
expect(logged_memory.count(:
|
474
|
+
expect(logged_memory.count(:features)).to be(1)
|
475
|
+
expect(logged_memory.count(:get_multi)).to be(1)
|
475
476
|
|
476
477
|
get '/', {}, 'flipper' => flipper
|
477
478
|
expect(logged_cached.count(:get_all)).to be(2)
|
478
|
-
expect(logged_memory.count(:
|
479
|
+
expect(logged_memory.count(:features)).to be(1)
|
480
|
+
expect(logged_memory.count(:get_multi)).to be(1)
|
479
481
|
|
480
482
|
get '/', {}, 'flipper' => flipper
|
481
483
|
expect(logged_cached.count(:get_all)).to be(3)
|
482
|
-
expect(logged_memory.count(:
|
484
|
+
expect(logged_memory.count(:features)).to be(1)
|
485
|
+
expect(logged_memory.count(:get_multi)).to be(1)
|
483
486
|
end
|
484
487
|
end
|
485
488
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'ice_age'
|
2
2
|
require 'json'
|
3
3
|
require 'rack/test'
|
4
|
+
require 'rack/session'
|
4
5
|
|
5
6
|
module SpecHelpers
|
6
7
|
extend self
|
@@ -12,7 +13,7 @@ module SpecHelpers
|
|
12
13
|
|
13
14
|
def build_app(flipper, options = {})
|
14
15
|
Flipper::UI.app(flipper, options) do |builder|
|
15
|
-
builder.use Rack::Session::Cookie, secret: 'test'
|
16
|
+
builder.use Rack::Session::Cookie, secret: 'test' * 16 # Rack 3+ wants a 64-character secret
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
@@ -29,10 +29,15 @@ end
|
|
29
29
|
|
30
30
|
class TestHelpTest < ActionDispatch::SystemTestCase
|
31
31
|
# Any driver that runs the app in a separate thread will test what we want here.
|
32
|
-
driven_by :cuprite
|
32
|
+
driven_by :cuprite, options: { process_timeout: 60 }
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
setup do
|
35
|
+
# Reconfigure Flipper since other tests change the adapter.
|
36
|
+
flipper_configure
|
37
|
+
|
38
|
+
# Ensure this test uses this app instance
|
39
|
+
Rails.application = TestApp.instance
|
40
|
+
end
|
36
41
|
|
37
42
|
test "configures a shared adapter between tests and app" do
|
38
43
|
Flipper.disable(:test)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- lib/flipper/actor.rb
|
91
91
|
- lib/flipper/adapter.rb
|
92
92
|
- lib/flipper/adapter_builder.rb
|
93
|
+
- lib/flipper/adapters/cache_base.rb
|
93
94
|
- lib/flipper/adapters/dual_write.rb
|
94
95
|
- lib/flipper/adapters/failover.rb
|
95
96
|
- lib/flipper/adapters/failsafe.rb
|
@@ -109,6 +110,7 @@ files:
|
|
109
110
|
- lib/flipper/adapters/sync/feature_synchronizer.rb
|
110
111
|
- lib/flipper/adapters/sync/interval_synchronizer.rb
|
111
112
|
- lib/flipper/adapters/sync/synchronizer.rb
|
113
|
+
- lib/flipper/adapters/wrapper.rb
|
112
114
|
- lib/flipper/cli.rb
|
113
115
|
- lib/flipper/cloud.rb
|
114
116
|
- lib/flipper/cloud/configuration.rb
|
@@ -193,6 +195,8 @@ files:
|
|
193
195
|
- lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb
|
194
196
|
- lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb
|
195
197
|
- lib/generators/flipper/update_generator.rb
|
198
|
+
- package-lock.json
|
199
|
+
- package.json
|
196
200
|
- spec/fixtures/environment.rb
|
197
201
|
- spec/fixtures/feature.json
|
198
202
|
- spec/fixtures/flipper_pstore_1679087600.json
|
@@ -286,8 +290,8 @@ files:
|
|
286
290
|
- spec/flipper_spec.rb
|
287
291
|
- spec/spec_helper.rb
|
288
292
|
- spec/support/actor_names.yml
|
289
|
-
- spec/support/climate_control.rb
|
290
293
|
- spec/support/descriptions.yml
|
294
|
+
- spec/support/fail_on_output.rb
|
291
295
|
- spec/support/fake_backoff_policy.rb
|
292
296
|
- spec/support/fake_udp_socket.rb
|
293
297
|
- spec/support/skippable.rb
|
@@ -307,7 +311,7 @@ metadata:
|
|
307
311
|
homepage_uri: https://www.flippercloud.io
|
308
312
|
source_code_uri: https://github.com/flippercloud/flipper
|
309
313
|
bug_tracker_uri: https://github.com/flippercloud/flipper/issues
|
310
|
-
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.
|
314
|
+
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.0.pre
|
311
315
|
post_install_message:
|
312
316
|
rdoc_options: []
|
313
317
|
require_paths:
|
@@ -421,8 +425,8 @@ test_files:
|
|
421
425
|
- spec/flipper_spec.rb
|
422
426
|
- spec/spec_helper.rb
|
423
427
|
- spec/support/actor_names.yml
|
424
|
-
- spec/support/climate_control.rb
|
425
428
|
- spec/support/descriptions.yml
|
429
|
+
- spec/support/fail_on_output.rb
|
426
430
|
- spec/support/fake_backoff_policy.rb
|
427
431
|
- spec/support/fake_udp_socket.rb
|
428
432
|
- spec/support/skippable.rb
|