flipper 0.10.2 → 0.11.0.beta1
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/.rubocop.yml +42 -0
- data/.rubocop_todo.yml +188 -0
- data/Changelog.md +10 -0
- data/Gemfile +6 -3
- data/README.md +4 -3
- data/Rakefile +13 -13
- data/docs/Adapters.md +2 -1
- data/docs/DockerCompose.md +6 -3
- data/docs/Gates.md +25 -3
- data/docs/Optimization.md +27 -5
- data/docs/api/README.md +73 -32
- data/docs/http/README.md +34 -0
- data/docs/read-only/README.md +22 -0
- data/examples/percentage_of_actors_group.rb +49 -0
- data/flipper.gemspec +15 -15
- data/lib/flipper.rb +2 -5
- data/lib/flipper/adapter.rb +10 -0
- data/lib/flipper/adapters/http.rb +147 -0
- data/lib/flipper/adapters/http/client.rb +83 -0
- data/lib/flipper/adapters/http/error.rb +14 -0
- data/lib/flipper/adapters/instrumented.rb +36 -36
- data/lib/flipper/adapters/memoizable.rb +2 -6
- data/lib/flipper/adapters/memory.rb +10 -9
- data/lib/flipper/adapters/operation_logger.rb +1 -1
- data/lib/flipper/adapters/pstore.rb +12 -11
- data/lib/flipper/adapters/read_only.rb +6 -6
- data/lib/flipper/dsl.rb +1 -3
- data/lib/flipper/feature.rb +11 -16
- data/lib/flipper/gate.rb +3 -3
- data/lib/flipper/gate_values.rb +6 -6
- data/lib/flipper/gates/group.rb +2 -2
- data/lib/flipper/gates/percentage_of_actors.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/metriks.rb +1 -1
- data/lib/flipper/instrumentation/statsd.rb +1 -1
- data/lib/flipper/instrumentation/statsd_subscriber.rb +1 -3
- data/lib/flipper/instrumentation/subscriber.rb +11 -10
- data/lib/flipper/instrumenters/memory.rb +1 -5
- data/lib/flipper/instrumenters/noop.rb +1 -1
- data/lib/flipper/middleware/memoizer.rb +11 -27
- data/lib/flipper/middleware/setup_env.rb +44 -0
- data/lib/flipper/registry.rb +8 -10
- data/lib/flipper/spec/shared_adapter_specs.rb +45 -67
- data/lib/flipper/test/shared_adapter_test.rb +25 -31
- data/lib/flipper/typecast.rb +2 -2
- data/lib/flipper/types/actor.rb +2 -4
- data/lib/flipper/types/group.rb +1 -1
- data/lib/flipper/types/percentage.rb +2 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/fixtures/feature.json +31 -0
- data/spec/flipper/adapters/http_spec.rb +148 -0
- data/spec/flipper/adapters/instrumented_spec.rb +20 -20
- data/spec/flipper/adapters/memoizable_spec.rb +59 -59
- data/spec/flipper/adapters/operation_logger_spec.rb +16 -16
- data/spec/flipper/adapters/pstore_spec.rb +6 -6
- data/spec/flipper/adapters/read_only_spec.rb +28 -34
- data/spec/flipper/dsl_spec.rb +73 -84
- data/spec/flipper/feature_check_context_spec.rb +27 -27
- data/spec/flipper/feature_spec.rb +186 -196
- data/spec/flipper/gate_spec.rb +11 -11
- data/spec/flipper/gate_values_spec.rb +46 -45
- data/spec/flipper/gates/actor_spec.rb +2 -2
- data/spec/flipper/gates/boolean_spec.rb +24 -23
- data/spec/flipper/gates/group_spec.rb +19 -19
- data/spec/flipper/gates/percentage_of_actors_spec.rb +10 -10
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +11 -11
- data/spec/flipper/instrumenters/memory_spec.rb +5 -5
- data/spec/flipper/instrumenters/noop_spec.rb +6 -6
- data/spec/flipper/middleware/memoizer_spec.rb +83 -100
- data/spec/flipper/middleware/setup_env_spec.rb +76 -0
- data/spec/flipper/registry_spec.rb +35 -39
- data/spec/flipper/typecast_spec.rb +18 -18
- data/spec/flipper/types/actor_spec.rb +30 -29
- data/spec/flipper/types/boolean_spec.rb +8 -8
- data/spec/flipper/types/group_spec.rb +28 -28
- data/spec/flipper/types/percentage_spec.rb +14 -14
- data/spec/flipper_spec.rb +61 -54
- data/spec/helper.rb +26 -21
- data/spec/integration_spec.rb +121 -113
- data/spec/support/fake_udp_socket.rb +1 -1
- data/spec/support/spec_helpers.rb +32 -4
- data/test/adapters/pstore_test.rb +3 -3
- data/test/test_helper.rb +1 -1
- metadata +20 -5
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
RSpec.describe Flipper::Middleware::SetupEnv do
|
4
|
+
context 'with flipper instance' do
|
5
|
+
let(:app) do
|
6
|
+
app = lambda do |env|
|
7
|
+
[200, { 'Content-Type' => 'text/html' }, [env['flipper'].object_id.to_s]]
|
8
|
+
end
|
9
|
+
builder = Rack::Builder.new
|
10
|
+
builder.use described_class, flipper
|
11
|
+
builder.run app
|
12
|
+
builder
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'sets flipper in env' do
|
16
|
+
get '/'
|
17
|
+
expect(last_response.body).to eq(flipper.object_id.to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with block that returns flipper instance' do
|
22
|
+
let(:flipper_block) do
|
23
|
+
-> { flipper }
|
24
|
+
end
|
25
|
+
let(:app) do
|
26
|
+
app = lambda do |env|
|
27
|
+
[200, { 'Content-Type' => 'text/html' }, [env['flipper'].object_id.to_s]]
|
28
|
+
end
|
29
|
+
builder = Rack::Builder.new
|
30
|
+
builder.use described_class, flipper_block
|
31
|
+
builder.run app
|
32
|
+
builder
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'sets flipper in env' do
|
36
|
+
get '/'
|
37
|
+
expect(last_response.body).to eq(flipper.object_id.to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when env already has flipper setup' do
|
42
|
+
let(:app) do
|
43
|
+
app = lambda do |env|
|
44
|
+
[200, { 'Content-Type' => 'text/html' }, [env['flipper'].object_id.to_s]]
|
45
|
+
end
|
46
|
+
builder = Rack::Builder.new
|
47
|
+
builder.use described_class, flipper
|
48
|
+
builder.run app
|
49
|
+
builder
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'leaves env flipper alone' do
|
53
|
+
env_flipper = build_flipper
|
54
|
+
get '/', {}, 'flipper' => env_flipper
|
55
|
+
expect(last_response.body).to eq(env_flipper.object_id.to_s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when flipper instance is nil' do
|
60
|
+
let(:app) do
|
61
|
+
app = lambda do |env|
|
62
|
+
[200, { 'Content-Type' => 'text/html' }, [env['flipper'].object_id.to_s]]
|
63
|
+
end
|
64
|
+
builder = Rack::Builder.new
|
65
|
+
builder.use described_class, nil
|
66
|
+
builder.run app
|
67
|
+
builder
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'leaves env flipper alone' do
|
71
|
+
env_flipper = build_flipper
|
72
|
+
get '/', {}, 'flipper' => env_flipper
|
73
|
+
expect(last_response.body).to eq(env_flipper.object_id.to_s)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -2,123 +2,119 @@ require 'helper'
|
|
2
2
|
require 'flipper/registry'
|
3
3
|
|
4
4
|
RSpec.describe Flipper::Registry do
|
5
|
-
subject {
|
5
|
+
subject { described_class.new(source) }
|
6
6
|
|
7
7
|
let(:source) { {} }
|
8
8
|
|
9
|
-
describe
|
10
|
-
it
|
9
|
+
describe '#add' do
|
10
|
+
it 'adds to source' do
|
11
11
|
value = 'thing'
|
12
12
|
subject.add(:admins, value)
|
13
13
|
expect(source[:admins]).to eq(value)
|
14
14
|
end
|
15
15
|
|
16
|
-
it
|
16
|
+
it 'converts key to symbol' do
|
17
17
|
value = 'thing'
|
18
18
|
subject.add('admins', value)
|
19
19
|
expect(source[:admins]).to eq(value)
|
20
20
|
end
|
21
21
|
|
22
|
-
it
|
22
|
+
it 'raises exception if key already registered' do
|
23
23
|
subject.add(:admins, 'thing')
|
24
24
|
|
25
|
-
expect
|
25
|
+
expect do
|
26
26
|
subject.add('admins', 'again')
|
27
|
-
|
27
|
+
end.to raise_error(Flipper::Registry::DuplicateKey)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
describe
|
32
|
-
context
|
31
|
+
describe '#get' do
|
32
|
+
context 'key registered' do
|
33
33
|
before do
|
34
34
|
source[:admins] = 'thing'
|
35
35
|
end
|
36
36
|
|
37
|
-
it
|
37
|
+
it 'returns value' do
|
38
38
|
expect(subject.get(:admins)).to eq('thing')
|
39
39
|
end
|
40
40
|
|
41
|
-
it
|
41
|
+
it 'returns value if given string key' do
|
42
42
|
expect(subject.get('admins')).to eq('thing')
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
context
|
47
|
-
it
|
48
|
-
expect
|
49
|
-
subject.get(:admins)
|
50
|
-
}.to raise_error(Flipper::Registry::KeyNotFound)
|
46
|
+
context 'key not registered' do
|
47
|
+
it 'returns nil' do
|
48
|
+
expect(subject.get(:admins)).to be(nil)
|
51
49
|
end
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
|
-
describe
|
53
|
+
describe '#key?' do
|
56
54
|
before do
|
57
|
-
source[:admins] =
|
55
|
+
source[:admins] = 'admins'
|
58
56
|
end
|
59
57
|
|
60
|
-
it
|
58
|
+
it 'returns true if the key exists' do
|
61
59
|
expect(subject.key?(:admins)).to eq true
|
62
60
|
end
|
63
61
|
|
64
|
-
it
|
62
|
+
it 'returns false if the key does not exists' do
|
65
63
|
expect(subject.key?(:unknown_key)).to eq false
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
69
|
-
describe
|
67
|
+
describe '#each' do
|
70
68
|
before do
|
71
69
|
source[:admins] = 'admins'
|
72
70
|
source[:devs] = 'devs'
|
73
71
|
end
|
74
72
|
|
75
|
-
it
|
73
|
+
it 'iterates source keys and values' do
|
76
74
|
results = {}
|
77
75
|
subject.each do |key, value|
|
78
76
|
results[key] = value
|
79
77
|
end
|
80
|
-
expect(results).to eq(
|
81
|
-
|
82
|
-
:devs => 'devs',
|
83
|
-
})
|
78
|
+
expect(results).to eq(admins: 'admins',
|
79
|
+
devs: 'devs')
|
84
80
|
end
|
85
81
|
end
|
86
82
|
|
87
|
-
describe
|
83
|
+
describe '#keys' do
|
88
84
|
before do
|
89
85
|
source[:admins] = 'admins'
|
90
86
|
source[:devs] = 'devs'
|
91
87
|
end
|
92
88
|
|
93
|
-
it
|
94
|
-
expect(subject.keys.map(&:to_s).sort).to eq(
|
89
|
+
it 'returns the keys' do
|
90
|
+
expect(subject.keys.map(&:to_s).sort).to eq(%w(admins devs))
|
95
91
|
end
|
96
92
|
|
97
|
-
it
|
93
|
+
it 'returns the keys as symbols' do
|
98
94
|
subject.keys.each do |key|
|
99
95
|
expect(key).to be_instance_of(Symbol)
|
100
96
|
end
|
101
97
|
end
|
102
98
|
end
|
103
99
|
|
104
|
-
describe
|
100
|
+
describe '#values' do
|
105
101
|
before do
|
106
102
|
source[:admins] = 'admins'
|
107
103
|
source[:devs] = 'devs'
|
108
104
|
end
|
109
105
|
|
110
|
-
it
|
111
|
-
expect(subject.values.map(&:to_s).sort).to eq(
|
106
|
+
it 'returns the values' do
|
107
|
+
expect(subject.values.map(&:to_s).sort).to eq(%w(admins devs))
|
112
108
|
end
|
113
109
|
end
|
114
110
|
|
115
|
-
describe
|
111
|
+
describe 'enumeration' do
|
116
112
|
before do
|
117
113
|
source[:admins] = 'admins'
|
118
114
|
source[:devs] = 'devs'
|
119
115
|
end
|
120
116
|
|
121
|
-
it
|
117
|
+
it 'works' do
|
122
118
|
keys = []
|
123
119
|
values = []
|
124
120
|
|
@@ -127,17 +123,17 @@ RSpec.describe Flipper::Registry do
|
|
127
123
|
values << value
|
128
124
|
end
|
129
125
|
|
130
|
-
expect(keys.map(&:to_s).sort).to eq(
|
131
|
-
expect(values.sort).to eq(
|
126
|
+
expect(keys.map(&:to_s).sort).to eq(%w(admins devs))
|
127
|
+
expect(values.sort).to eq(%w(admins devs))
|
132
128
|
end
|
133
129
|
end
|
134
130
|
|
135
|
-
describe
|
131
|
+
describe '#clear' do
|
136
132
|
before do
|
137
133
|
source[:admins] = 'admins'
|
138
134
|
end
|
139
135
|
|
140
|
-
it
|
136
|
+
it 'clears the source' do
|
141
137
|
subject.clear
|
142
138
|
expect(source).to be_empty
|
143
139
|
end
|
@@ -4,15 +4,15 @@ require 'flipper/typecast'
|
|
4
4
|
RSpec.describe Flipper::Typecast do
|
5
5
|
{
|
6
6
|
nil => false,
|
7
|
-
|
7
|
+
'' => false,
|
8
8
|
0 => false,
|
9
9
|
1 => true,
|
10
|
-
|
11
|
-
|
10
|
+
'0' => false,
|
11
|
+
'1' => true,
|
12
12
|
true => true,
|
13
13
|
false => false,
|
14
|
-
|
15
|
-
|
14
|
+
'true' => true,
|
15
|
+
'false' => false,
|
16
16
|
}.each do |value, expected|
|
17
17
|
context "#to_boolean for #{value.inspect}" do
|
18
18
|
it "returns #{expected}" do
|
@@ -23,11 +23,11 @@ RSpec.describe Flipper::Typecast do
|
|
23
23
|
|
24
24
|
{
|
25
25
|
nil => 0,
|
26
|
-
|
26
|
+
'' => 0,
|
27
27
|
0 => 0,
|
28
28
|
1 => 1,
|
29
|
-
|
30
|
-
|
29
|
+
'1' => 1,
|
30
|
+
'99' => 99,
|
31
31
|
}.each do |value, expected|
|
32
32
|
context "#to_integer for #{value.inspect}" do
|
33
33
|
it "returns #{expected}" do
|
@@ -38,9 +38,9 @@ RSpec.describe Flipper::Typecast do
|
|
38
38
|
|
39
39
|
{
|
40
40
|
nil => Set.new,
|
41
|
-
|
41
|
+
'' => Set.new,
|
42
42
|
Set.new([1, 2]) => Set.new([1, 2]),
|
43
|
-
[1, 2] => Set.new([1, 2])
|
43
|
+
[1, 2] => Set.new([1, 2]),
|
44
44
|
}.each do |value, expected|
|
45
45
|
context "#to_set for #{value.inspect}" do
|
46
46
|
it "returns #{expected}" do
|
@@ -49,15 +49,15 @@ RSpec.describe Flipper::Typecast do
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
it
|
53
|
-
expect
|
54
|
-
described_class.to_integer([
|
55
|
-
|
52
|
+
it 'raises argument error for integer value that cannot be converted to an integer' do
|
53
|
+
expect do
|
54
|
+
described_class.to_integer(['asdf'])
|
55
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
|
56
56
|
end
|
57
57
|
|
58
|
-
it
|
59
|
-
expect
|
60
|
-
described_class.to_set(
|
61
|
-
|
58
|
+
it 'raises argument error for set value that cannot be converted to a set' do
|
59
|
+
expect do
|
60
|
+
described_class.to_set('asdf')
|
61
|
+
end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
|
62
62
|
end
|
63
63
|
end
|
@@ -2,13 +2,13 @@ require 'helper'
|
|
2
2
|
require 'flipper/types/actor'
|
3
3
|
|
4
4
|
RSpec.describe Flipper::Types::Actor do
|
5
|
-
subject
|
5
|
+
subject do
|
6
6
|
thing = thing_class.new('2')
|
7
7
|
described_class.new(thing)
|
8
|
-
|
8
|
+
end
|
9
9
|
|
10
|
-
let(:thing_class)
|
11
|
-
Class.new
|
10
|
+
let(:thing_class) do
|
11
|
+
Class.new do
|
12
12
|
attr_reader :flipper_id
|
13
13
|
|
14
14
|
def initialize(flipper_id)
|
@@ -18,37 +18,37 @@ RSpec.describe Flipper::Types::Actor do
|
|
18
18
|
def admin?
|
19
19
|
true
|
20
20
|
end
|
21
|
-
|
22
|
-
|
21
|
+
end
|
22
|
+
end
|
23
23
|
|
24
|
-
describe
|
25
|
-
it
|
24
|
+
describe '.wrappable?' do
|
25
|
+
it 'returns true if actor' do
|
26
26
|
thing = thing_class.new('1')
|
27
27
|
actor = described_class.new(thing)
|
28
28
|
expect(described_class.wrappable?(actor)).to eq(true)
|
29
29
|
end
|
30
30
|
|
31
|
-
it
|
31
|
+
it 'returns true if responds to flipper_id' do
|
32
32
|
thing = thing_class.new(10)
|
33
33
|
expect(described_class.wrappable?(thing)).to eq(true)
|
34
34
|
end
|
35
35
|
|
36
|
-
it
|
36
|
+
it 'returns false if nil' do
|
37
37
|
expect(described_class.wrappable?(nil)).to be(false)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
describe
|
42
|
-
context
|
43
|
-
it
|
41
|
+
describe '.wrap' do
|
42
|
+
context 'for actor' do
|
43
|
+
it 'returns actor' do
|
44
44
|
actor = described_class.wrap(subject)
|
45
45
|
expect(actor).to be_instance_of(described_class)
|
46
46
|
expect(actor).to be(subject)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
context
|
51
|
-
it
|
50
|
+
context 'for other thing' do
|
51
|
+
it 'returns actor' do
|
52
52
|
thing = thing_class.new('1')
|
53
53
|
actor = described_class.wrap(thing)
|
54
54
|
expect(actor).to be_instance_of(described_class)
|
@@ -56,57 +56,58 @@ RSpec.describe Flipper::Types::Actor do
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
it
|
59
|
+
it 'initializes with thing that responds to id' do
|
60
60
|
thing = thing_class.new('1')
|
61
61
|
actor = described_class.new(thing)
|
62
62
|
expect(actor.value).to eq('1')
|
63
63
|
end
|
64
64
|
|
65
|
-
it
|
66
|
-
expect
|
65
|
+
it 'raises error when initialized with nil' do
|
66
|
+
expect do
|
67
67
|
described_class.new(nil)
|
68
|
-
|
68
|
+
end.to raise_error(ArgumentError)
|
69
69
|
end
|
70
70
|
|
71
|
-
it
|
71
|
+
it 'raises error when initalized with non-wrappable object' do
|
72
72
|
unwrappable_thing = Struct.new(:id).new(1)
|
73
|
-
expect
|
73
|
+
expect do
|
74
74
|
described_class.new(unwrappable_thing)
|
75
|
-
|
75
|
+
end.to raise_error(ArgumentError,
|
76
|
+
"#{unwrappable_thing.inspect} must respond to flipper_id, but does not")
|
76
77
|
end
|
77
78
|
|
78
|
-
it
|
79
|
+
it 'converts id to string' do
|
79
80
|
thing = thing_class.new(2)
|
80
81
|
actor = described_class.new(thing)
|
81
82
|
expect(actor.value).to eq('2')
|
82
83
|
end
|
83
84
|
|
84
|
-
it
|
85
|
+
it 'proxies everything to thing' do
|
85
86
|
thing = thing_class.new(10)
|
86
87
|
actor = described_class.new(thing)
|
87
88
|
expect(actor.admin?).to eq(true)
|
88
89
|
end
|
89
90
|
|
90
|
-
it
|
91
|
+
it 'exposes thing' do
|
91
92
|
thing = thing_class.new(10)
|
92
93
|
actor = described_class.new(thing)
|
93
94
|
expect(actor.thing).to be(thing)
|
94
95
|
end
|
95
96
|
|
96
|
-
describe
|
97
|
-
it
|
97
|
+
describe '#respond_to?' do
|
98
|
+
it 'returns true if responds to method' do
|
98
99
|
thing = thing_class.new('1')
|
99
100
|
actor = described_class.new(thing)
|
100
101
|
expect(actor.respond_to?(:value)).to eq(true)
|
101
102
|
end
|
102
103
|
|
103
|
-
it
|
104
|
+
it 'returns true if thing responds to method' do
|
104
105
|
thing = thing_class.new(10)
|
105
106
|
actor = described_class.new(thing)
|
106
107
|
expect(actor.respond_to?(:admin?)).to eq(true)
|
107
108
|
end
|
108
109
|
|
109
|
-
it
|
110
|
+
it 'returns false if does not respond to method and thing does not respond to method' do
|
110
111
|
thing = thing_class.new(10)
|
111
112
|
actor = described_class.new(thing)
|
112
113
|
expect(actor.respond_to?(:frankenstein)).to eq(false)
|