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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -2
- data/.github/workflows/examples.yml +8 -2
- data/Changelog.md +1 -647
- data/Gemfile +3 -2
- data/README.md +3 -1
- data/Rakefile +2 -2
- data/docs/images/banner.jpg +0 -0
- data/exe/flipper +5 -0
- data/flipper.gemspec +5 -1
- data/lib/flipper/adapters/actor_limit.rb +28 -0
- data/lib/flipper/adapters/cache_base.rb +143 -0
- data/lib/flipper/adapters/http/client.rb +25 -16
- data/lib/flipper/adapters/operation_logger.rb +18 -88
- data/lib/flipper/adapters/read_only.rb +6 -39
- data/lib/flipper/adapters/strict.rb +16 -18
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +263 -0
- data/lib/flipper/cloud/configuration.rb +9 -4
- data/lib/flipper/cloud/middleware.rb +5 -5
- data/lib/flipper/cloud/telemetry/instrumenter.rb +4 -8
- data/lib/flipper/cloud/telemetry/submitter.rb +2 -2
- data/lib/flipper/cloud/telemetry.rb +10 -2
- data/lib/flipper/cloud.rb +1 -1
- data/lib/flipper/engine.rb +32 -17
- data/lib/flipper/instrumentation/log_subscriber.rb +12 -3
- data/lib/flipper/metadata.rb +3 -1
- 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 +43 -0
- data/lib/flipper/typecast.rb +3 -3
- data/lib/flipper/version.rb +11 -1
- data/lib/flipper.rb +3 -1
- data/lib/generators/flipper/setup_generator.rb +63 -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 +1 -2
- data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +102 -76
- data/spec/flipper/adapters/strict_spec.rb +11 -9
- data/spec/flipper/cli_spec.rb +164 -0
- data/spec/flipper/cloud/configuration_spec.rb +35 -36
- data/spec/flipper/cloud/dsl_spec.rb +5 -5
- data/spec/flipper/cloud/middleware_spec.rb +8 -8
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +8 -9
- data/spec/flipper/cloud/telemetry/submitter_spec.rb +24 -24
- data/spec/flipper/cloud/telemetry_spec.rb +53 -1
- data/spec/flipper/cloud_spec.rb +10 -9
- data/spec/flipper/engine_spec.rb +140 -58
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +9 -2
- data/spec/flipper/middleware/memoizer_spec.rb +7 -4
- data/spec/flipper_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/fail_on_output.rb +8 -0
- data/spec/support/spec_helpers.rb +12 -5
- data/test/adapters/actor_limit_test.rb +20 -0
- data/test_rails/generators/flipper/setup_generator_test.rb +64 -0
- data/test_rails/system/test_help_test.rb +51 -0
- metadata +31 -9
- data/spec/support/climate_control.rb +0 -7
@@ -104,19 +104,19 @@ module Flipper
|
|
104
104
|
actor22 = Flipper::Actor.new('22')
|
105
105
|
actor_asdf = Flipper::Actor.new('asdf')
|
106
106
|
|
107
|
-
assert_equal true, @
|
108
|
-
assert_equal true, @
|
107
|
+
assert_equal true, @feature.enable(actor22)
|
108
|
+
assert_equal true, @feature.enable(actor_asdf)
|
109
109
|
|
110
|
-
|
111
|
-
|
110
|
+
assert @feature.enabled?(actor22)
|
111
|
+
assert @feature.enabled?(actor_asdf)
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
assert_equal true, @feature.disable(actor22)
|
114
|
+
refute @feature.enabled?(actor22)
|
115
|
+
assert @feature.enabled?(actor_asdf)
|
116
116
|
|
117
|
-
assert_equal true, @
|
118
|
-
|
119
|
-
|
117
|
+
assert_equal true, @feature.disable(actor_asdf)
|
118
|
+
refute @feature.enabled?(actor22)
|
119
|
+
refute @feature.enabled?(actor_asdf)
|
120
120
|
end
|
121
121
|
|
122
122
|
def test_can_enable_disable_get_value_for_percentage_of_actors_gate
|
@@ -178,10 +178,10 @@ module Flipper
|
|
178
178
|
end
|
179
179
|
|
180
180
|
def test_converts_the_actor_value_to_a_string
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
actor = Flipper::Actor.new(22)
|
182
|
+
refute @feature.enabled?(actor)
|
183
|
+
@feature.enable_actor actor
|
184
|
+
assert @feature.enabled?(actor)
|
185
185
|
end
|
186
186
|
|
187
187
|
def test_converts_group_value_to_a_string
|
@@ -292,9 +292,9 @@ module Flipper
|
|
292
292
|
|
293
293
|
def test_can_double_enable_an_actor_without_error
|
294
294
|
actor = Flipper::Actor.new('Flipper::Actor;22')
|
295
|
-
assert_equal true, @
|
296
|
-
assert_equal true, @
|
297
|
-
|
295
|
+
assert_equal true, @feature.enable(actor)
|
296
|
+
assert_equal true, @feature.enable(actor)
|
297
|
+
assert @feature.enabled?(actor)
|
298
298
|
end
|
299
299
|
|
300
300
|
def test_can_double_enable_a_group_without_error
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Flipper
|
2
|
+
module TestHelp
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def flipper_configure
|
6
|
+
# Use a shared Memory adapter for all tests. This is instantiated outside of the
|
7
|
+
# `configure` block so the same instance is returned in new threads.
|
8
|
+
adapter = Flipper::Adapters::Memory.new
|
9
|
+
|
10
|
+
Flipper.configure do |config|
|
11
|
+
config.adapter { adapter }
|
12
|
+
config.default { Flipper.new(config.adapter) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def flipper_reset
|
17
|
+
# Remove all features
|
18
|
+
Flipper.features.each(&:remove) rescue nil
|
19
|
+
|
20
|
+
# Reset previous DSL instance
|
21
|
+
Flipper.instance = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if defined?(RSpec) && RSpec.respond_to?(:configure)
|
27
|
+
RSpec.configure do |config|
|
28
|
+
config.include Flipper::TestHelp
|
29
|
+
config.before(:suite) { Flipper::TestHelp.flipper_configure }
|
30
|
+
config.before(:each) { flipper_reset }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
if defined?(ActiveSupport)
|
34
|
+
ActiveSupport.on_load(:active_support_test_case) do
|
35
|
+
Flipper::TestHelp.flipper_configure
|
36
|
+
|
37
|
+
ActiveSupport::TestCase.class_eval do
|
38
|
+
include Flipper::TestHelp
|
39
|
+
|
40
|
+
setup :flipper_reset
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/flipper/typecast.rb
CHANGED
@@ -3,8 +3,8 @@ require "flipper/serializers/json"
|
|
3
3
|
require "flipper/serializers/gzip"
|
4
4
|
|
5
5
|
module Flipper
|
6
|
-
|
7
|
-
|
6
|
+
class Typecast
|
7
|
+
TRUTH_MAP = {
|
8
8
|
true => true,
|
9
9
|
1 => true,
|
10
10
|
'true' => true,
|
@@ -15,7 +15,7 @@ module Flipper
|
|
15
15
|
#
|
16
16
|
# Returns true or false.
|
17
17
|
def self.to_boolean(value)
|
18
|
-
!!
|
18
|
+
!!TRUTH_MAP[value]
|
19
19
|
end
|
20
20
|
|
21
21
|
# Internal: Convert value to an integer.
|
data/lib/flipper/version.rb
CHANGED
@@ -1,3 +1,13 @@
|
|
1
1
|
module Flipper
|
2
|
-
VERSION = '1.
|
2
|
+
VERSION = '1.3.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
|
data/lib/flipper.rb
CHANGED
@@ -174,10 +174,12 @@ end
|
|
174
174
|
|
175
175
|
require 'flipper/actor'
|
176
176
|
require 'flipper/adapter'
|
177
|
+
require 'flipper/adapters/wrapper'
|
178
|
+
require 'flipper/adapters/actor_limit'
|
179
|
+
require 'flipper/adapters/instrumented'
|
177
180
|
require 'flipper/adapters/memoizable'
|
178
181
|
require 'flipper/adapters/memory'
|
179
182
|
require 'flipper/adapters/strict'
|
180
|
-
require 'flipper/adapters/instrumented'
|
181
183
|
require 'flipper/adapter_builder'
|
182
184
|
require 'flipper/configuration'
|
183
185
|
require 'flipper/dsl'
|
@@ -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
|
data/package-lock.json
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
"name": "flipper",
|
3
|
+
"lockfileVersion": 3,
|
4
|
+
"requires": true,
|
5
|
+
"packages": {
|
6
|
+
"": {
|
7
|
+
"hasInstallScript": true,
|
8
|
+
"dependencies": {
|
9
|
+
"@popperjs/core": "^2.11.8",
|
10
|
+
"bootstrap": "^5.3.3"
|
11
|
+
}
|
12
|
+
},
|
13
|
+
"node_modules/@popperjs/core": {
|
14
|
+
"version": "2.11.8",
|
15
|
+
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
16
|
+
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
17
|
+
"funding": {
|
18
|
+
"type": "opencollective",
|
19
|
+
"url": "https://opencollective.com/popperjs"
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"node_modules/bootstrap": {
|
23
|
+
"version": "5.3.3",
|
24
|
+
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
25
|
+
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
26
|
+
"funding": [
|
27
|
+
{
|
28
|
+
"type": "github",
|
29
|
+
"url": "https://github.com/sponsors/twbs"
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"type": "opencollective",
|
33
|
+
"url": "https://opencollective.com/bootstrap"
|
34
|
+
}
|
35
|
+
],
|
36
|
+
"peerDependencies": {
|
37
|
+
"@popperjs/core": "^2.11.8"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
data/package.json
ADDED
@@ -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(
|
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,20 @@
|
|
1
|
+
require "flipper/adapters/actor_limit"
|
2
|
+
|
3
|
+
RSpec.describe Flipper::Adapters::ActorLimit do
|
4
|
+
it_should_behave_like 'a flipper adapter' do
|
5
|
+
let(:limit) { 5 }
|
6
|
+
let(:adapter) { Flipper::Adapters::ActorLimit.new(Flipper::Adapters::Memory.new, limit) }
|
7
|
+
|
8
|
+
subject { adapter }
|
9
|
+
|
10
|
+
describe '#enable' do
|
11
|
+
it "fails when limit exceeded" do
|
12
|
+
5.times { |i| feature.enable Flipper::Actor.new("User;#{i}") }
|
13
|
+
|
14
|
+
expect {
|
15
|
+
feature.enable Flipper::Actor.new("User;6")
|
16
|
+
}.to raise_error(Flipper::Adapters::ActorLimit::LimitExceeded)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -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
|
@@ -1,86 +1,104 @@
|
|
1
1
|
require 'flipper/adapters/http'
|
2
2
|
require 'flipper/adapters/pstore'
|
3
|
-
require 'rack/handler/webrick'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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'
|
4
|
+
rack_handler = begin
|
5
|
+
# Rack 3+
|
6
|
+
require 'rackup/handler/webrick'
|
7
|
+
Rackup::Handler::WEBrick
|
8
|
+
rescue LoadError
|
9
|
+
require 'rack/handler/webrick'
|
10
|
+
Rack::Handler::WEBrick
|
11
|
+
end
|
46
12
|
|
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
13
|
|
52
|
-
|
53
|
-
expect(flipper[:search].groups_value).to eq(Set.new)
|
54
|
-
end
|
14
|
+
FLIPPER_SPEC_API_PORT = ENV.fetch('FLIPPER_SPEC_API_PORT', 9001).to_i
|
55
15
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
16
|
+
RSpec.describe Flipper::Adapters::Http do
|
17
|
+
default_options = {
|
18
|
+
url: "http://localhost:#{FLIPPER_SPEC_API_PORT}",
|
19
|
+
}
|
20
|
+
|
21
|
+
{
|
22
|
+
basic: default_options.dup,
|
23
|
+
gzip: default_options.dup.merge(headers: { 'accept-encoding': 'gzip' }),
|
24
|
+
}.each do |name, options|
|
25
|
+
context "adapter (#{name} #{options.inspect})" do
|
26
|
+
subject do
|
27
|
+
described_class.new(options)
|
28
|
+
end
|
29
|
+
|
30
|
+
before :all do
|
31
|
+
dir = FlipperRoot.join('tmp').tap(&:mkpath)
|
32
|
+
log_path = dir.join('flipper_adapters_http_spec.log')
|
33
|
+
@pstore_file = dir.join('flipper.pstore')
|
34
|
+
@pstore_file.unlink if @pstore_file.exist?
|
35
|
+
|
36
|
+
api_adapter = Flipper::Adapters::PStore.new(@pstore_file)
|
37
|
+
flipper_api = Flipper.new(api_adapter)
|
38
|
+
app = Flipper::Api.app(flipper_api)
|
39
|
+
server_options = {
|
40
|
+
Port: FLIPPER_SPEC_API_PORT,
|
41
|
+
StartCallback: -> { @started = true },
|
42
|
+
Logger: WEBrick::Log.new(log_path.to_s, WEBrick::Log::INFO),
|
43
|
+
AccessLog: [
|
44
|
+
[log_path.open('w'), WEBrick::AccessLog::COMBINED_LOG_FORMAT],
|
45
|
+
],
|
46
|
+
}
|
47
|
+
@server = WEBrick::HTTPServer.new(server_options)
|
48
|
+
@server.mount '/', rack_handler, app
|
49
|
+
|
50
|
+
Thread.new { @server.start }
|
51
|
+
Timeout.timeout(1) { :wait until @started }
|
52
|
+
end
|
53
|
+
|
54
|
+
after :all do
|
55
|
+
@server.shutdown if @server
|
56
|
+
end
|
57
|
+
|
58
|
+
before(:each) do
|
59
|
+
@pstore_file.unlink if @pstore_file.exist?
|
60
|
+
end
|
61
|
+
|
62
|
+
it_should_behave_like 'a flipper adapter'
|
63
|
+
|
64
|
+
it "can enable and disable unregistered group" do
|
65
|
+
flipper = Flipper.new(subject)
|
66
|
+
expect(flipper[:search].enable_group(:some_made_up_group)).to be(true)
|
67
|
+
expect(flipper[:search].groups_value).to eq(Set["some_made_up_group"])
|
68
|
+
|
69
|
+
expect(flipper[:search].disable_group(:some_made_up_group)).to be(true)
|
70
|
+
expect(flipper[:search].groups_value).to eq(Set.new)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "can import" do
|
74
|
+
adapter = Flipper::Adapters::Memory.new
|
75
|
+
source_flipper = Flipper.new(adapter)
|
76
|
+
source_flipper.enable_percentage_of_actors :search, 10
|
77
|
+
source_flipper.enable_percentage_of_time :search, 15
|
78
|
+
source_flipper.enable_actor :search, Flipper::Actor.new('User;1')
|
79
|
+
source_flipper.enable_actor :search, Flipper::Actor.new('User;100')
|
80
|
+
source_flipper.enable_group :search, :admins
|
81
|
+
source_flipper.enable_group :search, :employees
|
82
|
+
source_flipper.enable :plausible
|
83
|
+
source_flipper.disable :google_analytics
|
84
|
+
|
85
|
+
flipper = Flipper.new(subject)
|
86
|
+
flipper.import(source_flipper)
|
87
|
+
expect(flipper[:search].percentage_of_actors_value).to be(10)
|
88
|
+
expect(flipper[:search].percentage_of_time_value).to be(15)
|
89
|
+
expect(flipper[:search].actors_value).to eq(Set["User;1", "User;100"])
|
90
|
+
expect(flipper[:search].groups_value).to eq(Set["admins", "employees"])
|
91
|
+
expect(flipper[:plausible].boolean_value).to be(true)
|
92
|
+
expect(flipper[:google_analytics].boolean_value).to be(false)
|
93
|
+
end
|
76
94
|
end
|
77
95
|
end
|
78
96
|
|
79
97
|
it "sends default headers" do
|
80
98
|
headers = {
|
81
|
-
'
|
82
|
-
'
|
83
|
-
'
|
99
|
+
'accept' => 'application/json',
|
100
|
+
'content-type' => 'application/json',
|
101
|
+
'user-agent' => "Flipper HTTP Adapter v#{Flipper::VERSION}",
|
84
102
|
}
|
85
103
|
stub_request(:get, "http://app.com/flipper/features/feature_panel")
|
86
104
|
.with(headers: headers)
|
@@ -94,9 +112,17 @@ RSpec.describe Flipper::Adapters::Http do
|
|
94
112
|
stub_const("Rails", double(version: "7.1.0"))
|
95
113
|
stub_const("Sinatra::VERSION", "3.1.0")
|
96
114
|
stub_const("Hanami::VERSION", "0.7.2")
|
115
|
+
stub_const("GoodJob::VERSION", "3.21.5")
|
116
|
+
stub_const("Sidekiq::VERSION", "7.2.0")
|
97
117
|
|
98
118
|
headers = {
|
99
|
-
"
|
119
|
+
"client-framework" => [
|
120
|
+
"rails=7.1.0",
|
121
|
+
"sinatra=3.1.0",
|
122
|
+
"hanami=0.7.2",
|
123
|
+
"good_job=3.21.5",
|
124
|
+
"sidekiq=7.2.0",
|
125
|
+
]
|
100
126
|
}
|
101
127
|
|
102
128
|
stub_request(:get, "http://app.com/flipper/features/feature_panel")
|
@@ -112,7 +138,7 @@ RSpec.describe Flipper::Adapters::Http do
|
|
112
138
|
stub_const("Sinatra::VERSION", "3.1.0")
|
113
139
|
|
114
140
|
headers = {
|
115
|
-
"
|
141
|
+
"client-framework" => ["rails=7.1.0", "sinatra=3.1.0"]
|
116
142
|
}
|
117
143
|
|
118
144
|
stub_request(:get, "http://app.com/flipper/features/feature_panel")
|
@@ -280,7 +306,7 @@ RSpec.describe Flipper::Adapters::Http do
|
|
280
306
|
let(:options) do
|
281
307
|
{
|
282
308
|
url: 'http://app.com/mount-point',
|
283
|
-
headers: { '
|
309
|
+
headers: { 'x-custom-header' => 'foo' },
|
284
310
|
basic_auth_username: 'username',
|
285
311
|
basic_auth_password: 'password',
|
286
312
|
read_timeout: 100,
|
@@ -301,7 +327,7 @@ RSpec.describe Flipper::Adapters::Http do
|
|
301
327
|
subject.get(feature)
|
302
328
|
expect(
|
303
329
|
a_request(:get, 'http://app.com/mount-point/features/feature_panel')
|
304
|
-
.with(headers: { '
|
330
|
+
.with(headers: { 'x-custom-header' => 'foo' })
|
305
331
|
).to have_been_made.once
|
306
332
|
end
|
307
333
|
|
@@ -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
|
-
|
10
|
-
|
9
|
+
[true, :raise].each do |handler|
|
10
|
+
context "handler = #{handler}" do
|
11
|
+
subject { described_class.new(Flipper::Adapters::Memory.new, handler) }
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|