flipper 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +25 -1
  3. data/.github/workflows/examples.yml +7 -1
  4. data/Changelog.md +1 -638
  5. data/Gemfile +5 -1
  6. data/README.md +21 -21
  7. data/Rakefile +2 -2
  8. data/exe/flipper +5 -0
  9. data/flipper.gemspec +6 -2
  10. data/lib/flipper/adapters/http/client.rb +25 -16
  11. data/lib/flipper/adapters/strict.rb +11 -8
  12. data/lib/flipper/cli.rb +240 -0
  13. data/lib/flipper/cloud/configuration.rb +7 -1
  14. data/lib/flipper/cloud/middleware.rb +5 -5
  15. data/lib/flipper/cloud/telemetry/submitter.rb +2 -2
  16. data/lib/flipper/cloud.rb +1 -1
  17. data/lib/flipper/engine.rb +32 -17
  18. data/lib/flipper/instrumentation/log_subscriber.rb +12 -3
  19. data/lib/flipper/metadata.rb +3 -1
  20. data/lib/flipper/test_help.rb +36 -0
  21. data/lib/flipper/version.rb +11 -1
  22. data/lib/generators/flipper/setup_generator.rb +63 -0
  23. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  24. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  25. data/lib/generators/flipper/update_generator.rb +35 -0
  26. data/spec/fixtures/environment.rb +1 -0
  27. data/spec/flipper/adapter_builder_spec.rb +1 -2
  28. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  29. data/spec/flipper/adapters/http_spec.rb +92 -75
  30. data/spec/flipper/adapters/strict_spec.rb +11 -9
  31. data/spec/flipper/cli_spec.rb +164 -0
  32. data/spec/flipper/cloud/configuration_spec.rb +9 -2
  33. data/spec/flipper/cloud/dsl_spec.rb +5 -5
  34. data/spec/flipper/cloud/middleware_spec.rb +8 -8
  35. data/spec/flipper/cloud/telemetry/submitter_spec.rb +24 -24
  36. data/spec/flipper/cloud/telemetry_spec.rb +1 -1
  37. data/spec/flipper/cloud_spec.rb +4 -4
  38. data/spec/flipper/engine_spec.rb +76 -11
  39. data/spec/flipper/instrumentation/log_subscriber_spec.rb +9 -2
  40. data/spec/flipper_spec.rb +1 -1
  41. data/spec/spec_helper.rb +1 -0
  42. data/spec/support/spec_helpers.rb +10 -4
  43. data/test_rails/generators/flipper/setup_generator_test.rb +64 -0
  44. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  45. data/test_rails/helper.rb +19 -2
  46. data/test_rails/system/test_help_test.rb +46 -0
  47. metadata +25 -8
@@ -1,4 +1,5 @@
1
1
  require 'securerandom'
2
+ require 'active_support/gem_version'
2
3
  require 'active_support/notifications'
3
4
  require 'active_support/log_subscriber'
4
5
 
@@ -71,11 +72,19 @@ module Flipper
71
72
  self.class.logger
72
73
  end
73
74
 
75
+ def self.attach
76
+ attach_to InstrumentationNamespace
77
+ end
78
+
79
+ def self.detach
80
+ # Rails 5.2 doesn't support this, that's fine
81
+ detach_from InstrumentationNamespace if respond_to?(:detach_from)
82
+ end
83
+
74
84
  private
75
85
 
76
86
  # Rails 7.1 changed the signature of this function.
77
- # Checking if > 7.0.99 rather than >= 7.1 so that 7.1 pre-release versions are included.
78
- COLOR_OPTIONS = if Rails.gem_version > Gem::Version.new('7.0.99')
87
+ COLOR_OPTIONS = if Gem::Requirement.new(">=7.1").satisfied_by?(ActiveSupport.gem_version)
79
88
  { bold: true }.freeze
80
89
  else
81
90
  true
@@ -88,5 +97,5 @@ module Flipper
88
97
  end
89
98
  end
90
99
 
91
- Instrumentation::LogSubscriber.attach_to InstrumentationNamespace
100
+ Instrumentation::LogSubscriber.attach
92
101
  end
@@ -1,9 +1,11 @@
1
+ require_relative './version'
2
+
1
3
  module Flipper
2
4
  METADATA = {
3
5
  "documentation_uri" => "https://www.flippercloud.io/docs",
4
6
  "homepage_uri" => "https://www.flippercloud.io",
5
7
  "source_code_uri" => "https://github.com/flippercloud/flipper",
6
8
  "bug_tracker_uri" => "https://github.com/flippercloud/flipper/issues",
7
- "changelog_uri" => "https://github.com/flippercloud/flipper/blob/main/Changelog.md",
9
+ "changelog_uri" => "https://github.com/flippercloud/flipper/releases/tag/v#{Flipper::VERSION}",
8
10
  }.freeze
9
11
  end
@@ -0,0 +1,36 @@
1
+ module Flipper
2
+ module TestHelp
3
+ def flipper_configure
4
+ # Create a single shared memory adapter instance for each test
5
+ @flipper_adapter = Flipper::Adapters::Memory.new
6
+
7
+ Flipper.configure do |config|
8
+ config.adapter { @flipper_adapter }
9
+ config.default { Flipper.new(config.adapter) }
10
+ end
11
+ end
12
+
13
+ def flipper_reset
14
+ Flipper.instance = nil # Reset previous flipper instance
15
+ end
16
+ end
17
+ end
18
+
19
+ if defined?(RSpec)
20
+ RSpec.configure do |config|
21
+ config.include Flipper::TestHelp
22
+ config.before(:all) { flipper_configure }
23
+ config.before(:each) { flipper_reset }
24
+ end
25
+ end
26
+
27
+ if defined?(ActiveSupport)
28
+ ActiveSupport.on_load(:active_support_test_case) do
29
+ ActiveSupport::TestCase.class_eval do
30
+ include Flipper::TestHelp
31
+
32
+ setup :flipper_configure
33
+ setup :flipper_reset
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,13 @@
1
1
  module Flipper
2
- VERSION = '1.1.1'.freeze
2
+ VERSION = '1.2.0'.freeze
3
+
4
+ REQUIRED_RUBY_VERSION = '2.6'.freeze
5
+ NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
6
+
7
+ REQUIRED_RAILS_VERSION = '5.2'.freeze
8
+ NEXT_REQUIRED_RAILS_VERSION = '6.1.0'.freeze
9
+
10
+ def self.deprecated_ruby_version?
11
+ Gem::Version.new(RUBY_VERSION) < Gem::Version.new(NEXT_REQUIRED_RUBY_VERSION)
12
+ end
3
13
  end
@@ -0,0 +1,63 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module Flipper
4
+ module Generators
5
+ class SetupGenerator < ::Rails::Generators::Base
6
+ desc 'Peform any necessary steps to install Flipper'
7
+
8
+ class_option :token, type: :string, default: nil, aliases: '-t',
9
+ desc: "Your personal environment token for Flipper Cloud"
10
+
11
+ def generate_active_record
12
+ invoke 'flipper:active_record' if defined?(Flipper::Adapters::ActiveRecord)
13
+ end
14
+
15
+ def configure_cloud_token
16
+ return unless options[:token]
17
+
18
+ configure_with_dotenv || configure_with_credentials
19
+ end
20
+
21
+ private
22
+
23
+ def configure_with_dotenv
24
+ ['.env.development', '.env.local', '.env'].detect do |file|
25
+ next unless exists?(file)
26
+ append_to_file file, "\nFLIPPER_CLOUD_TOKEN=#{options[:token]}\n"
27
+ end
28
+ end
29
+
30
+ def configure_with_credentials
31
+ return unless exists?("config/credentials.yml.enc") && (ENV["RAILS_MASTER_KEY"] || exists?("config/master.key"))
32
+
33
+ content = "flipper:\n cloud_token: #{options[:token]}\n"
34
+ action InjectIntoEncryptedFile.new(self, Rails.application.credentials, content, after: /\z/)
35
+ end
36
+
37
+ # Check if a file exists in the destination root
38
+ def exists?(path)
39
+ File.exist?(File.expand_path(path, destination_root))
40
+ end
41
+
42
+ # Action to inject content into ActiveSupport::EncryptedFile
43
+ class InjectIntoEncryptedFile < Thor::Actions::InjectIntoFile
44
+ def initialize(base, encrypted_file, data, config)
45
+ @encrypted_file = encrypted_file
46
+ super(base, encrypted_file.content_path, data, config)
47
+ end
48
+
49
+ def content
50
+ @content ||= @encrypted_file.read
51
+ end
52
+
53
+ def replace!(regexp, string, force)
54
+ if force || !replacement_present?
55
+ success = content.gsub!(regexp, string)
56
+ @encrypted_file.write content unless pretend?
57
+ success
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ class CreateFlipperTables < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ create_table :flipper_features do |t|
4
+ t.string :key, null: false
5
+ t.timestamps null: false
6
+ end
7
+ add_index :flipper_features, :key, unique: true
8
+
9
+ create_table :flipper_gates do |t|
10
+ t.string :feature_key, null: false
11
+ t.string :key, null: false
12
+ t.string :value
13
+ t.timestamps null: false
14
+ end
15
+ add_index :flipper_gates, [:feature_key, :key, :value], unique: true
16
+ end
17
+
18
+ def down
19
+ drop_table :flipper_gates
20
+ drop_table :flipper_features
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChangeFlipperGatesValueToText < ActiveRecord::Migration<%= migration_version %>
4
+ def up
5
+ # Ensure this incremental update migration is idempotent
6
+ return unless connection.column_exists? :flipper_gates, :value, :string
7
+
8
+ if index_exists? :flipper_gates, [:feature_key, :key, :value]
9
+ remove_index :flipper_gates, [:feature_key, :key, :value]
10
+ end
11
+ change_column :flipper_gates, :value, :text
12
+ add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: { value: 255 }
13
+ end
14
+
15
+ def down
16
+ change_column :flipper_gates, :value, :string
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+
6
+ module Flipper
7
+ module Generators
8
+ #
9
+ # Rails generator used for updating Flipper in a Rails application.
10
+ # Run it with +bin/rails g flipper:update+ in your console.
11
+ #
12
+ class UpdateGenerator < Rails::Generators::Base
13
+ include ActiveRecord::Generators::Migration
14
+
15
+ TEMPLATES = File.join(File.dirname(__FILE__), 'templates/update')
16
+ source_paths << TEMPLATES
17
+
18
+ # Generates incremental migration files unless they already exist.
19
+ # All migrations should be idempotent e.g. +add_index+ is guarded with +if_index_exists?+
20
+ def update_migration_files
21
+ migration_templates = Dir.children(File.join(TEMPLATES, 'migrations')).sort
22
+ migration_templates.each do |template_file|
23
+ destination_file = template_file.match(/^\d*_(.*\.rb)/)[1] # 01_create_flipper_tables.rb.erb => create_flipper_tables.rb
24
+ migration_template "migrations/#{template_file}", File.join(db_migrate_path, destination_file), skip: true
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def migration_version
31
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1 @@
1
+ # Placeholder for config/environment.rb
@@ -38,10 +38,9 @@ RSpec.describe Flipper::AdapterBuilder do
38
38
  strict_adapter = memoizable_adapter.adapter
39
39
  memory_adapter = strict_adapter.adapter
40
40
 
41
-
42
41
  expect(memoizable_adapter).to be_instance_of(Flipper::Adapters::Memoizable)
43
42
  expect(strict_adapter).to be_instance_of(Flipper::Adapters::Strict)
44
- expect(strict_adapter.handler).to be(Flipper::Adapters::Strict::HANDLERS.fetch(:warn))
43
+ expect(strict_adapter.handler).to be(:warn)
45
44
  expect(memory_adapter).to be_instance_of(Flipper::Adapters::Memory)
46
45
  end
47
46
 
@@ -0,0 +1,61 @@
1
+ require "flipper/adapters/http/client"
2
+
3
+ RSpec.describe Flipper::Adapters::Http::Client do
4
+ describe "#initialize" do
5
+ it "requires url" do
6
+ expect { described_class.new }.to raise_error(KeyError, "key not found: :url")
7
+ end
8
+
9
+ it "sets default headers" do
10
+ client = described_class.new(url: "http://example.com")
11
+ expect(client.headers).to eq({
12
+ 'content-type' => 'application/json',
13
+ 'accept' => 'application/json',
14
+ 'user-agent' => "Flipper HTTP Adapter v#{Flipper::VERSION}",
15
+ })
16
+ end
17
+
18
+ it "adds custom headers" do
19
+ client = described_class.new(url: "http://example.com", headers: {'custom-header' => 'value'})
20
+ expect(client.headers).to include('custom-header' => 'value')
21
+ end
22
+
23
+ it "overrides default headers with custom headers" do
24
+ client = described_class.new(url: "http://example.com", headers: {'content-type' => 'text/plain'})
25
+ expect(client.headers['content-type']).to eq('text/plain')
26
+ end
27
+ end
28
+
29
+ describe "#add_header" do
30
+ it "can add string header" do
31
+ client = described_class.new(url: "http://example.com")
32
+ client.add_header("key", "value")
33
+ expect(client.headers.fetch("key")).to eq("value")
34
+ end
35
+
36
+ it "standardizes key to lowercase" do
37
+ client = described_class.new(url: "http://example.com")
38
+ client.add_header("Content-Type", "value")
39
+ expect(client.headers.fetch("content-type")).to eq("value")
40
+ end
41
+
42
+ it "standardizes key to dashes" do
43
+ client = described_class.new(url: "http://example.com")
44
+ client.add_header(:content_type, "value")
45
+ expect(client.headers.fetch("content-type")).to eq("value")
46
+ end
47
+
48
+ it "can add symbol header" do
49
+ client = described_class.new(url: "http://example.com")
50
+ client.add_header(:key, "value")
51
+ expect(client.headers.fetch("key")).to eq("value")
52
+ end
53
+
54
+ it "overrides existing header" do
55
+ client = described_class.new(url: "http://example.com")
56
+ client.add_header("key", "value 1")
57
+ client.add_header("key", "value 2")
58
+ expect(client.headers.fetch("key")).to eq("value 2")
59
+ end
60
+ end
61
+ end
@@ -5,82 +5,91 @@ require 'rack/handler/webrick'
5
5
  FLIPPER_SPEC_API_PORT = ENV.fetch('FLIPPER_SPEC_API_PORT', 9001).to_i
6
6
 
7
7
  RSpec.describe Flipper::Adapters::Http do
8
- context 'adapter' do
9
- subject do
10
- described_class.new(url: "http://localhost:#{FLIPPER_SPEC_API_PORT}")
11
- end
12
-
13
- before :all do
14
- dir = FlipperRoot.join('tmp').tap(&:mkpath)
15
- log_path = dir.join('flipper_adapters_http_spec.log')
16
- @pstore_file = dir.join('flipper.pstore')
17
- @pstore_file.unlink if @pstore_file.exist?
18
-
19
- api_adapter = Flipper::Adapters::PStore.new(@pstore_file)
20
- flipper_api = Flipper.new(api_adapter)
21
- app = Flipper::Api.app(flipper_api)
22
- server_options = {
23
- Port: FLIPPER_SPEC_API_PORT,
24
- StartCallback: -> { @started = true },
25
- Logger: WEBrick::Log.new(log_path.to_s, WEBrick::Log::INFO),
26
- AccessLog: [
27
- [log_path.open('w'), WEBrick::AccessLog::COMBINED_LOG_FORMAT],
28
- ],
29
- }
30
- @server = WEBrick::HTTPServer.new(server_options)
31
- @server.mount '/', Rack::Handler::WEBrick, app
32
-
33
- Thread.new { @server.start }
34
- Timeout.timeout(1) { :wait until @started }
35
- end
36
-
37
- after :all do
38
- @server.shutdown if @server
39
- end
40
-
41
- before(:each) do
42
- @pstore_file.unlink if @pstore_file.exist?
43
- end
44
-
45
- it_should_behave_like 'a flipper adapter'
46
-
47
- it "can enable and disable unregistered group" do
48
- flipper = Flipper.new(subject)
49
- expect(flipper[:search].enable_group(:some_made_up_group)).to be(true)
50
- expect(flipper[:search].groups_value).to eq(Set["some_made_up_group"])
51
-
52
- expect(flipper[:search].disable_group(:some_made_up_group)).to be(true)
53
- expect(flipper[:search].groups_value).to eq(Set.new)
54
- end
55
-
56
- it "can import" do
57
- adapter = Flipper::Adapters::Memory.new
58
- source_flipper = Flipper.new(adapter)
59
- source_flipper.enable_percentage_of_actors :search, 10
60
- source_flipper.enable_percentage_of_time :search, 15
61
- source_flipper.enable_actor :search, Flipper::Actor.new('User;1')
62
- source_flipper.enable_actor :search, Flipper::Actor.new('User;100')
63
- source_flipper.enable_group :search, :admins
64
- source_flipper.enable_group :search, :employees
65
- source_flipper.enable :plausible
66
- source_flipper.disable :google_analytics
67
-
68
- flipper = Flipper.new(subject)
69
- flipper.import(source_flipper)
70
- expect(flipper[:search].percentage_of_actors_value).to be(10)
71
- expect(flipper[:search].percentage_of_time_value).to be(15)
72
- expect(flipper[:search].actors_value).to eq(Set["User;1", "User;100"])
73
- expect(flipper[:search].groups_value).to eq(Set["admins", "employees"])
74
- expect(flipper[:plausible].boolean_value).to be(true)
75
- expect(flipper[:google_analytics].boolean_value).to be(false)
8
+ default_options = {
9
+ url: "http://localhost:#{FLIPPER_SPEC_API_PORT}",
10
+ }
11
+
12
+ {
13
+ basic: default_options.dup,
14
+ gzip: default_options.dup.merge(headers: { 'accept-encoding': 'gzip' }),
15
+ }.each do |name, options|
16
+ context "adapter (#{name} #{options.inspect})" do
17
+ subject do
18
+ described_class.new(options)
19
+ end
20
+
21
+ before :all do
22
+ dir = FlipperRoot.join('tmp').tap(&:mkpath)
23
+ log_path = dir.join('flipper_adapters_http_spec.log')
24
+ @pstore_file = dir.join('flipper.pstore')
25
+ @pstore_file.unlink if @pstore_file.exist?
26
+
27
+ api_adapter = Flipper::Adapters::PStore.new(@pstore_file)
28
+ flipper_api = Flipper.new(api_adapter)
29
+ app = Flipper::Api.app(flipper_api)
30
+ server_options = {
31
+ Port: FLIPPER_SPEC_API_PORT,
32
+ StartCallback: -> { @started = true },
33
+ Logger: WEBrick::Log.new(log_path.to_s, WEBrick::Log::INFO),
34
+ AccessLog: [
35
+ [log_path.open('w'), WEBrick::AccessLog::COMBINED_LOG_FORMAT],
36
+ ],
37
+ }
38
+ @server = WEBrick::HTTPServer.new(server_options)
39
+ @server.mount '/', Rack::Handler::WEBrick, app
40
+
41
+ Thread.new { @server.start }
42
+ Timeout.timeout(1) { :wait until @started }
43
+ end
44
+
45
+ after :all do
46
+ @server.shutdown if @server
47
+ end
48
+
49
+ before(:each) do
50
+ @pstore_file.unlink if @pstore_file.exist?
51
+ end
52
+
53
+ it_should_behave_like 'a flipper adapter'
54
+
55
+ it "can enable and disable unregistered group" do
56
+ flipper = Flipper.new(subject)
57
+ expect(flipper[:search].enable_group(:some_made_up_group)).to be(true)
58
+ expect(flipper[:search].groups_value).to eq(Set["some_made_up_group"])
59
+
60
+ expect(flipper[:search].disable_group(:some_made_up_group)).to be(true)
61
+ expect(flipper[:search].groups_value).to eq(Set.new)
62
+ end
63
+
64
+ it "can import" do
65
+ adapter = Flipper::Adapters::Memory.new
66
+ source_flipper = Flipper.new(adapter)
67
+ source_flipper.enable_percentage_of_actors :search, 10
68
+ source_flipper.enable_percentage_of_time :search, 15
69
+ source_flipper.enable_actor :search, Flipper::Actor.new('User;1')
70
+ source_flipper.enable_actor :search, Flipper::Actor.new('User;100')
71
+ source_flipper.enable_group :search, :admins
72
+ source_flipper.enable_group :search, :employees
73
+ source_flipper.enable :plausible
74
+ source_flipper.disable :google_analytics
75
+
76
+ flipper = Flipper.new(subject)
77
+ flipper.import(source_flipper)
78
+ expect(flipper[:search].percentage_of_actors_value).to be(10)
79
+ expect(flipper[:search].percentage_of_time_value).to be(15)
80
+ expect(flipper[:search].actors_value).to eq(Set["User;1", "User;100"])
81
+ expect(flipper[:search].groups_value).to eq(Set["admins", "employees"])
82
+ expect(flipper[:plausible].boolean_value).to be(true)
83
+ expect(flipper[:google_analytics].boolean_value).to be(false)
84
+ end
76
85
  end
77
86
  end
78
87
 
79
88
  it "sends default headers" do
80
89
  headers = {
81
- 'Accept' => 'application/json',
82
- 'Content-Type' => 'application/json',
83
- 'User-Agent' => "Flipper HTTP Adapter v#{Flipper::VERSION}",
90
+ 'accept' => 'application/json',
91
+ 'content-type' => 'application/json',
92
+ 'user-agent' => "Flipper HTTP Adapter v#{Flipper::VERSION}",
84
93
  }
85
94
  stub_request(:get, "http://app.com/flipper/features/feature_panel")
86
95
  .with(headers: headers)
@@ -94,9 +103,17 @@ RSpec.describe Flipper::Adapters::Http do
94
103
  stub_const("Rails", double(version: "7.1.0"))
95
104
  stub_const("Sinatra::VERSION", "3.1.0")
96
105
  stub_const("Hanami::VERSION", "0.7.2")
106
+ stub_const("GoodJob::VERSION", "3.21.5")
107
+ stub_const("Sidekiq::VERSION", "7.2.0")
97
108
 
98
109
  headers = {
99
- "Client-Framework" => ["rails=7.1.0", "sinatra=3.1.0", "hanami=0.7.2"]
110
+ "client-framework" => [
111
+ "rails=7.1.0",
112
+ "sinatra=3.1.0",
113
+ "hanami=0.7.2",
114
+ "good_job=3.21.5",
115
+ "sidekiq=7.2.0",
116
+ ]
100
117
  }
101
118
 
102
119
  stub_request(:get, "http://app.com/flipper/features/feature_panel")
@@ -112,7 +129,7 @@ RSpec.describe Flipper::Adapters::Http do
112
129
  stub_const("Sinatra::VERSION", "3.1.0")
113
130
 
114
131
  headers = {
115
- "Client-Framework" => ["rails=7.1.0", "sinatra=3.1.0"]
132
+ "client-framework" => ["rails=7.1.0", "sinatra=3.1.0"]
116
133
  }
117
134
 
118
135
  stub_request(:get, "http://app.com/flipper/features/feature_panel")
@@ -280,7 +297,7 @@ RSpec.describe Flipper::Adapters::Http do
280
297
  let(:options) do
281
298
  {
282
299
  url: 'http://app.com/mount-point',
283
- headers: { 'X-Custom-Header' => 'foo' },
300
+ headers: { 'x-custom-header' => 'foo' },
284
301
  basic_auth_username: 'username',
285
302
  basic_auth_password: 'password',
286
303
  read_timeout: 100,
@@ -301,7 +318,7 @@ RSpec.describe Flipper::Adapters::Http do
301
318
  subject.get(feature)
302
319
  expect(
303
320
  a_request(:get, 'http://app.com/mount-point/features/feature_panel')
304
- .with(headers: { 'X-Custom-Header' => 'foo' })
321
+ .with(headers: { 'x-custom-header' => 'foo' })
305
322
  ).to have_been_made.once
306
323
  end
307
324
 
@@ -6,18 +6,20 @@ RSpec.describe Flipper::Adapters::Strict do
6
6
  subject { described_class.new(Flipper::Adapters::Memory.new, :noop) }
7
7
  end
8
8
 
9
- context "handler = :raise" do
10
- subject { described_class.new(Flipper::Adapters::Memory.new, :raise) }
9
+ [true, :raise].each do |handler|
10
+ context "handler = #{handler}" do
11
+ subject { described_class.new(Flipper::Adapters::Memory.new, handler) }
11
12
 
12
- context "#get" do
13
- it "raises an error for unknown feature" do
14
- expect { subject.get(feature) }.to raise_error(Flipper::Adapters::Strict::NotFound)
13
+ context "#get" do
14
+ it "raises an error for unknown feature" do
15
+ expect { subject.get(feature) }.to raise_error(Flipper::Adapters::Strict::NotFound)
16
+ end
15
17
  end
16
- end
17
18
 
18
- context "#get_multi" do
19
- it "raises an error for unknown feature" do
20
- expect { subject.get_multi([feature]) }.to raise_error(Flipper::Adapters::Strict::NotFound)
19
+ context "#get_multi" do
20
+ it "raises an error for unknown feature" do
21
+ expect { subject.get_multi([feature]) }.to raise_error(Flipper::Adapters::Strict::NotFound)
22
+ end
21
23
  end
22
24
  end
23
25
  end