flipper 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -2
  3. data/.github/workflows/examples.yml +8 -2
  4. data/Changelog.md +1 -647
  5. data/Gemfile +3 -2
  6. data/README.md +3 -1
  7. data/Rakefile +2 -2
  8. data/docs/images/banner.jpg +0 -0
  9. data/exe/flipper +5 -0
  10. data/flipper.gemspec +5 -1
  11. data/lib/flipper/adapters/actor_limit.rb +28 -0
  12. data/lib/flipper/adapters/cache_base.rb +143 -0
  13. data/lib/flipper/adapters/http/client.rb +25 -16
  14. data/lib/flipper/adapters/operation_logger.rb +18 -88
  15. data/lib/flipper/adapters/read_only.rb +6 -39
  16. data/lib/flipper/adapters/strict.rb +16 -18
  17. data/lib/flipper/adapters/wrapper.rb +54 -0
  18. data/lib/flipper/cli.rb +263 -0
  19. data/lib/flipper/cloud/configuration.rb +9 -4
  20. data/lib/flipper/cloud/middleware.rb +5 -5
  21. data/lib/flipper/cloud/telemetry/instrumenter.rb +4 -8
  22. data/lib/flipper/cloud/telemetry/submitter.rb +2 -2
  23. data/lib/flipper/cloud/telemetry.rb +10 -2
  24. data/lib/flipper/cloud.rb +1 -1
  25. data/lib/flipper/engine.rb +32 -17
  26. data/lib/flipper/instrumentation/log_subscriber.rb +12 -3
  27. data/lib/flipper/metadata.rb +3 -1
  28. data/lib/flipper/poller.rb +6 -5
  29. data/lib/flipper/serializers/gzip.rb +3 -5
  30. data/lib/flipper/serializers/json.rb +3 -5
  31. data/lib/flipper/spec/shared_adapter_specs.rb +17 -16
  32. data/lib/flipper/test/shared_adapter_test.rb +17 -17
  33. data/lib/flipper/test_help.rb +43 -0
  34. data/lib/flipper/typecast.rb +3 -3
  35. data/lib/flipper/version.rb +11 -1
  36. data/lib/flipper.rb +3 -1
  37. data/lib/generators/flipper/setup_generator.rb +63 -0
  38. data/package-lock.json +41 -0
  39. data/package.json +10 -0
  40. data/spec/fixtures/environment.rb +1 -0
  41. data/spec/flipper/adapter_builder_spec.rb +1 -2
  42. data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
  43. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  44. data/spec/flipper/adapters/http_spec.rb +102 -76
  45. data/spec/flipper/adapters/strict_spec.rb +11 -9
  46. data/spec/flipper/cli_spec.rb +164 -0
  47. data/spec/flipper/cloud/configuration_spec.rb +35 -36
  48. data/spec/flipper/cloud/dsl_spec.rb +5 -5
  49. data/spec/flipper/cloud/middleware_spec.rb +8 -8
  50. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +8 -9
  51. data/spec/flipper/cloud/telemetry/submitter_spec.rb +24 -24
  52. data/spec/flipper/cloud/telemetry_spec.rb +53 -1
  53. data/spec/flipper/cloud_spec.rb +10 -9
  54. data/spec/flipper/engine_spec.rb +140 -58
  55. data/spec/flipper/instrumentation/log_subscriber_spec.rb +9 -2
  56. data/spec/flipper/middleware/memoizer_spec.rb +7 -4
  57. data/spec/flipper_spec.rb +1 -1
  58. data/spec/spec_helper.rb +1 -0
  59. data/spec/support/fail_on_output.rb +8 -0
  60. data/spec/support/spec_helpers.rb +12 -5
  61. data/test/adapters/actor_limit_test.rb +20 -0
  62. data/test_rails/generators/flipper/setup_generator_test.rb +64 -0
  63. data/test_rails/system/test_help_test.rb +51 -0
  64. metadata +31 -9
  65. data/spec/support/climate_control.rb +0 -7
@@ -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,86 +35,102 @@ 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
- with_env 'FLIPPER_STRICT' => 'true' do
37
- subject
38
- expect(config.strict).to eq(:raise)
39
- expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
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
- with_env 'FLIPPER_STRICT' => 'warn' do
45
- subject
46
- expect(config.strict).to eq(:warn)
47
- expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
48
- expect(adapter.handler).to be(Flipper::Adapters::Strict::HANDLERS.fetch(:warn))
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
- with_env 'FLIPPER_STRICT' => 'false' do
53
+ ENV['FLIPPER_STRICT'] = 'false'
54
+ subject
55
+ expect(config.strict).to eq(false)
56
+ expect(adapter).not_to be_instance_of(Flipper::Adapters::Strict)
57
+ end
58
+
59
+ [true, :raise, :warn].each do |value|
60
+ it "can set strict=#{value.inspect} in initializer" do
61
+ initializer { config.strict = value }
54
62
  subject
55
- expect(config.strict).to eq(false)
56
- expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
63
+ expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
64
+ expect(adapter.handler).to be(value)
57
65
  end
58
66
  end
59
67
 
60
- it "defaults to strict=false in RAILS_ENV=production" do
61
- Rails.env = "production"
62
- subject
63
- expect(config.strict).to eq(false)
64
- expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
68
+ it "can set strict=false in initializer" do
69
+ initializer { config.strict = false }
70
+ subject
71
+ expect(config.strict).to eq(false)
72
+ expect(adapter).not_to be_instance_of(Flipper::Adapters::Strict)
65
73
  end
66
74
 
67
- %w(development test).each do |env|
75
+ it "defaults to strict=:warn in RAILS_ENV=development" do
76
+ Rails.env = "development"
77
+ subject
78
+ expect(config.strict).to eq(:warn)
79
+ expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
80
+ end
81
+
82
+ %w(production test).each do |env|
68
83
  it "defaults to strict=warn in RAILS_ENV=#{env}" do
69
84
  Rails.env = env
70
85
  expect(Rails.env).to eq(env)
71
86
  subject
72
- expect(config.strict).to eq(:warn)
73
- expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
74
- expect(adapter.handler).to be(Flipper::Adapters::Strict::HANDLERS.fetch(:warn))
87
+ expect(config.strict).to eq(false)
88
+ expect(adapter).not_to be_instance_of(Flipper::Adapters::Strict)
75
89
  end
76
90
  end
91
+
92
+ it "defaults to strict=warn in RAILS_ENV=development" do
93
+ Rails.env = "development"
94
+ expect(Rails.env).to eq("development")
95
+ subject
96
+ expect(config.strict).to eq(:warn)
97
+ expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
98
+ expect(adapter.handler).to be(:warn)
99
+ end
77
100
  end
78
101
 
79
102
  context 'cloudless' do
80
103
  it_behaves_like 'config.strict'
81
104
 
82
105
  it 'can set env_key from ENV' do
83
- with_env 'FLIPPER_ENV_KEY' => 'flopper' do
84
- subject
85
- expect(config.env_key).to eq('flopper')
86
- end
106
+ ENV['FLIPPER_ENV_KEY'] = 'flopper'
107
+ subject
108
+ expect(config.env_key).to eq('flopper')
87
109
  end
88
110
 
89
111
  it 'can set memoize from ENV' do
90
- with_env 'FLIPPER_MEMOIZE' => 'false' do
91
- subject
92
- expect(config.memoize).to eq(false)
93
- end
112
+ ENV['FLIPPER_MEMOIZE'] = 'false'
113
+ subject
114
+ expect(config.memoize).to eq(false)
94
115
  end
95
116
 
96
117
  it 'can set preload from ENV' do
97
- with_env 'FLIPPER_PRELOAD' => 'false' do
98
- subject
99
- expect(config.preload).to eq(false)
100
- end
118
+ ENV['FLIPPER_PRELOAD'] = 'false'
119
+ subject
120
+ expect(config.preload).to eq(false)
101
121
  end
102
122
 
103
123
  it 'can set instrumenter from ENV' do
104
124
  stub_const('My::Cool::Instrumenter', Class.new)
105
- with_env 'FLIPPER_INSTRUMENTER' => 'My::Cool::Instrumenter' do
106
- subject
107
- expect(config.instrumenter).to eq(My::Cool::Instrumenter)
108
- end
125
+ ENV['FLIPPER_INSTRUMENTER'] = 'My::Cool::Instrumenter'
126
+ subject
127
+ expect(config.instrumenter).to eq(My::Cool::Instrumenter)
109
128
  end
110
129
 
111
130
  it 'can set log from ENV' do
112
- with_env 'FLIPPER_LOG' => 'false' do
113
- subject
114
- expect(config.log).to eq(false)
115
- end
131
+ ENV['FLIPPER_LOG'] = 'false'
132
+ subject
133
+ expect(config.log).to eq(false)
116
134
  end
117
135
 
118
136
  it 'sets defaults' do
@@ -153,13 +171,51 @@ RSpec.describe Flipper::Engine do
153
171
  if: nil
154
172
  })
155
173
  end
174
+
175
+ context "test_help" do
176
+ it "is loaded if RAILS_ENV=test" do
177
+ Rails.env = "test"
178
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
179
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
180
+ subject
181
+ expect(config.test_help).to eq(true)
182
+ end
183
+
184
+ it "is loaded if FLIPPER_TEST_HELP=true" do
185
+ ENV["FLIPPER_TEST_HELP"] = "true"
186
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
187
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
188
+ subject
189
+ expect(config.test_help).to eq(true)
190
+ end
191
+
192
+ it "is loaded if config.flipper.test_help = true" do
193
+ initializer { config.test_help = true }
194
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
195
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
196
+ subject
197
+ end
198
+
199
+ it "is not loaded if FLIPPER_TEST_HELP=false" do
200
+ ENV["FLIPPER_TEST_HELP"] = "false"
201
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
202
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help").never
203
+ subject
204
+ end
205
+
206
+ it "is not loaded if config.flipper.test_help = false" do
207
+ Rails.env = "true"
208
+ initializer { config.test_help = false }
209
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
210
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help").never
211
+ subject
212
+ end
213
+ end
156
214
  end
157
215
 
158
216
  context 'with cloud' do
159
- around do |example|
160
- with_env "FLIPPER_CLOUD_TOKEN" => "test-token" do
161
- example.run
162
- end
217
+ before do
218
+ ENV["FLIPPER_CLOUD_TOKEN"] = "test-token"
163
219
  end
164
220
 
165
221
  # App for Rack::Test
@@ -167,7 +223,8 @@ RSpec.describe Flipper::Engine do
167
223
 
168
224
  it_behaves_like 'config.strict' do
169
225
  let(:adapter) do
170
- dual_write = Flipper.adapter.adapter
226
+ memoizable = Flipper.adapter
227
+ dual_write = memoizable.adapter
171
228
  poll = dual_write.local
172
229
  poll.adapter
173
230
  end
@@ -179,14 +236,13 @@ RSpec.describe Flipper::Engine do
179
236
  application.initialize!
180
237
 
181
238
  expect(Flipper.instance).to be_a(Flipper::Cloud::DSL)
182
- expect(Flipper.instance.instrumenter).to be(ActiveSupport::Notifications)
239
+ expect(Flipper.instance.instrumenter).to be_a(Flipper::Cloud::Telemetry::Instrumenter)
240
+ expect(Flipper.instance.instrumenter.instrumenter).to be(ActiveSupport::Notifications)
183
241
  end
184
242
 
185
243
  context "with CLOUD_SYNC_SECRET" do
186
- around do |example|
187
- with_env "FLIPPER_CLOUD_SYNC_SECRET" => "test-secret" do
188
- example.run
189
- end
244
+ before do
245
+ ENV["FLIPPER_CLOUD_SYNC_SECRET"] = "test-secret"
190
246
  end
191
247
 
192
248
  let(:request_body) do
@@ -210,7 +266,7 @@ RSpec.describe Flipper::Engine do
210
266
  application.initialize!
211
267
 
212
268
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").with({
213
- headers: { "Flipper-Cloud-Token" => ENV["FLIPPER_CLOUD_TOKEN"] },
269
+ headers: { "flipper-cloud-token" => ENV["FLIPPER_CLOUD_TOKEN"] },
214
270
  }).to_return(status: 200, body: JSON.generate({ features: {} }), headers: {})
215
271
 
216
272
  post "/_flipper", request_body, { "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value }
@@ -231,10 +287,9 @@ RSpec.describe Flipper::Engine do
231
287
 
232
288
  context "without FLIPPER_CLOUD_TOKEN" do
233
289
  it "gracefully skips configuring webhook app" do
234
- with_env "FLIPPER_CLOUD_TOKEN" => nil do
235
- application.initialize!
236
- expect(Flipper.instance).to be_a(Flipper::DSL)
237
- end
290
+ ENV["FLIPPER_CLOUD_TOKEN"] = nil
291
+ application.initialize!
292
+ expect(Flipper.instance).to be_a(Flipper::DSL)
238
293
 
239
294
  post "/_flipper"
240
295
  expect(last_response.status).to eq(404)
@@ -282,6 +337,33 @@ RSpec.describe Flipper::Engine do
282
337
  expect(ActiveRecord::Base.ancestors).to include(Flipper::Model::ActiveRecord)
283
338
  end
284
339
 
340
+ describe "config.actor_limit" do
341
+ let(:adapter) do
342
+ application.initialize!
343
+ Flipper.adapter.adapter.adapter
344
+ end
345
+
346
+ it "defaults to 100" do
347
+ expect(adapter).to be_instance_of(Flipper::Adapters::ActorLimit)
348
+ expect(adapter.limit).to eq(100)
349
+ end
350
+
351
+ it "can be set from FLIPPER_ACTOR_LIMIT env" do
352
+ ENV["FLIPPER_ACTOR_LIMIT"] = "500"
353
+ expect(adapter.limit).to eq(500)
354
+ end
355
+
356
+ it "can be set from an initializer" do
357
+ initializer { config.actor_limit = 99 }
358
+ expect(adapter.limit).to eq(99)
359
+ end
360
+
361
+ it "can be disabled from an initializer" do
362
+ initializer { config.actor_limit = false }
363
+ expect(adapter).not_to be_instance_of(Flipper::Adapters::ActorLimit)
364
+ end
365
+ end
366
+
285
367
  # Add app initializer in the same order as config/initializers/*
286
368
  def initializer(&block)
287
369
  application.initializer 'spec', before: :load_config_initializers do
@@ -1,6 +1,6 @@
1
1
  require 'logger'
2
- require 'flipper/adapters/instrumented'
3
2
  require 'flipper/instrumentation/log_subscriber'
3
+ require 'flipper/adapters/instrumented'
4
4
 
5
5
  begin
6
6
  require 'active_support/isolated_execution_state'
@@ -8,6 +8,9 @@ rescue LoadError
8
8
  # ActiveSupport::IsolatedExecutionState is only available in Rails 5.2+
9
9
  end
10
10
 
11
+ # Don't log in other tests, we'll manually re-attach when this one starts
12
+ Flipper::Instrumentation::LogSubscriber.detach
13
+
11
14
  RSpec.describe Flipper::Instrumentation::LogSubscriber do
12
15
  let(:adapter) do
13
16
  memory = Flipper::Adapters::Memory.new
@@ -32,8 +35,12 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
32
35
  described_class.logger = nil
33
36
  end
34
37
 
38
+ before(:all) do
39
+ described_class.attach
40
+ end
41
+
35
42
  after(:all) do
36
- ActiveSupport::Notifications.unsubscribe("flipper")
43
+ described_class.detach
37
44
  end
38
45
 
39
46
  let(:log) { @io.string }
@@ -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, expires_in: 10)
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(:get_all)).to be(1)
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(:get_all)).to be(1)
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(:get_all)).to be(1)
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
data/spec/flipper_spec.rb CHANGED
@@ -241,7 +241,7 @@ RSpec.describe Flipper do
241
241
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
242
242
  with({
243
243
  headers: {
244
- 'Flipper-Cloud-Token'=>'asdf',
244
+ 'flipper-cloud-token'=>'asdf',
245
245
  },
246
246
  }).to_return(status: 200, body: '{"features": {}}', headers: {})
247
247
  cloud_configuration = Flipper::Cloud::Configuration.new({
data/spec/spec_helper.rb CHANGED
@@ -18,6 +18,7 @@ require 'flipper'
18
18
  require 'flipper/api'
19
19
  require 'flipper/spec/shared_adapter_specs'
20
20
  require 'flipper/ui'
21
+ require 'flipper/test_help'
21
22
 
22
23
  Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }
23
24
 
@@ -0,0 +1,8 @@
1
+ if ENV["CI"] || ENV["FAIL_ON_OUTPUT"]
2
+ RSpec.configure do |config|
3
+ config.around do |example|
4
+ output = silence { example.run }
5
+ fail "Use `silence { }` to avoid printing to STDOUT/STDERR\n#{output}" unless output.empty?
6
+ end
7
+ end
8
+ end
@@ -1,8 +1,11 @@
1
1
  require 'ice_age'
2
2
  require 'json'
3
3
  require 'rack/test'
4
+ require 'rack/session'
4
5
 
5
6
  module SpecHelpers
7
+ extend self
8
+
6
9
  def self.included(base)
7
10
  base.let(:flipper) { build_flipper }
8
11
  base.let(:app) { build_app(flipper) }
@@ -10,7 +13,7 @@ module SpecHelpers
10
13
 
11
14
  def build_app(flipper, options = {})
12
15
  Flipper::UI.app(flipper, options) do |builder|
13
- builder.use Rack::Session::Cookie, secret: 'test'
16
+ builder.use Rack::Session::Cookie, secret: 'test' * 16 # Rack 3+ wants a 64-character secret
14
17
  end
15
18
  end
16
19
 
@@ -27,7 +30,11 @@ module SpecHelpers
27
30
  end
28
31
 
29
32
  def json_response
30
- JSON.parse(last_response.body)
33
+ body = last_response.body
34
+ if last_response["content-encoding"] == 'gzip'
35
+ body = Flipper::Typecast.from_gzip(body)
36
+ end
37
+ JSON.parse(body)
31
38
  end
32
39
 
33
40
  def api_error_code_reference_url
@@ -76,11 +83,11 @@ module SpecHelpers
76
83
 
77
84
  yield
78
85
 
79
- $stderr = original_stderr
80
- $stdout = original_stdout
81
-
82
86
  # Return output
83
87
  output.string
88
+ ensure
89
+ $stderr = original_stderr
90
+ $stdout = original_stdout
84
91
  end
85
92
  end
86
93
 
@@ -0,0 +1,20 @@
1
+ require "test_helper"
2
+ require "flipper/test/shared_adapter_test"
3
+ require "flipper/adapters/actor_limit"
4
+
5
+ class Flipper::Adapters::ActorLimitTest < MiniTest::Test
6
+ prepend Flipper::Test::SharedAdapterTests
7
+
8
+ def setup
9
+ @memory = Flipper::Adapters::Memory.new
10
+ @adapter = Flipper::Adapters::ActorLimit.new(@memory, 5)
11
+ end
12
+
13
+ def test_enable_fails_when_limit_exceeded
14
+ 5.times { |i| @feature.enable Flipper::Actor.new("User;#{i}") }
15
+
16
+ assert_raises Flipper::Adapters::ActorLimit::LimitExceeded do
17
+ @feature.enable Flipper::Actor.new("User;6")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,64 @@
1
+ require "helper"
2
+ require "generators/flipper/setup_generator"
3
+
4
+ class SetupGeneratorTest < Rails::Generators::TestCase
5
+ tests Flipper::Generators::SetupGenerator
6
+ ROOT = File.expand_path("../../../tmp/generators", __dir__)
7
+ destination ROOT
8
+ setup :prepare_destination
9
+
10
+ test "invokes flipper:active_record generator if ActiveRecord adapter defined" do
11
+ begin
12
+ load 'flipper/adapters/active_record.rb'
13
+ run_generator
14
+ assert_migration "db/migrate/create_flipper_tables.rb"
15
+ ensure
16
+ Flipper::Adapters.send(:remove_const, :ActiveRecord)
17
+ end
18
+ end
19
+
20
+ test "does not invoke flipper:active_record generator if ActiveRecord adapter not defined" do
21
+ # Ensure adapter not defined
22
+ Flipper::Adapters.send(:remove_const, :ActiveRecord) rescue nil
23
+
24
+ run_generator
25
+ assert_no_migration "db/migrate/create_flipper_tables.rb"
26
+ end
27
+
28
+ %w(.env.development .env.local .env).each do |file|
29
+ test "configures Flipper Cloud token in #{file} if it exists" do
30
+ File.write("#{ROOT}/#{file}", "")
31
+ run_generator %w(--token abc123)
32
+ assert_file file, /^FLIPPER_CLOUD_TOKEN=abc123$/m
33
+ end
34
+ end
35
+
36
+ test "configures Flipper Cloud token in .env.development before .env" do
37
+ File.write("#{ROOT}/.env.development", "")
38
+ File.write("#{ROOT}/.env", "")
39
+
40
+ run_generator %w(--token abc123)
41
+ assert_file ".env.development", /^FLIPPER_CLOUD_TOKEN=abc123$/m
42
+ assert_file ".env", ""
43
+ end
44
+
45
+ test "does not write to .env if no token provided" do
46
+ File.write("#{ROOT}/.env", "")
47
+ run_generator
48
+ assert_file ".env", ""
49
+ end
50
+
51
+ test "configures Flipper Cloud token in config/credentials.yml.enc if credentials.yml.enc exist" do
52
+ Dir.chdir(ROOT) do
53
+ FileUtils.mkdir_p("config")
54
+ ENV["RAILS_MASTER_KEY"] = "a" * 32
55
+ Rails.application = Class.new(Rails::Application)
56
+ Rails.application.credentials.write("")
57
+
58
+ run_generator %w(--token abc123)
59
+ assert_file "config/credentials.yml.enc"
60
+ expected_config = { flipper: { cloud_token: "abc123" } }
61
+ assert_equal expected_config, Rails.application.credentials.config
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,51 @@
1
+ require_relative "../helper"
2
+
3
+ # Not worth trying to test on old Rails versions
4
+ return unless Rails::VERSION::MAJOR >= 7
5
+
6
+ require "capybara/cuprite"
7
+ require "flipper"
8
+ require "flipper/test_help"
9
+
10
+ require 'action_dispatch/system_testing/server'
11
+ ActionDispatch::SystemTesting::Server.silence_puma = true
12
+
13
+ class TestApp < Rails::Application
14
+ config.load_defaults "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"
15
+ config.eager_load = false
16
+ config.logger = ActiveSupport::Logger.new(StringIO.new)
17
+ routes.append do
18
+ root to: "features#index"
19
+ end
20
+ end
21
+
22
+ TestApp.initialize!
23
+
24
+ class FeaturesController < ActionController::Base
25
+ def index
26
+ render json: Flipper.enabled?(:test) ? "Enabled" : "Disabled"
27
+ end
28
+ end
29
+
30
+ class TestHelpTest < ActionDispatch::SystemTestCase
31
+ # Any driver that runs the app in a separate thread will test what we want here.
32
+ driven_by :cuprite, options: { process_timeout: 60 }
33
+
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
41
+
42
+ test "configures a shared adapter between tests and app" do
43
+ Flipper.disable(:test)
44
+ visit "/"
45
+ assert_selector "*", text: "Disabled"
46
+
47
+ Flipper.enable(:test)
48
+ visit "/"
49
+ assert_selector "*", text: "Enabled"
50
+ end
51
+ end