flipper 0.26.2 → 0.27.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +8 -0
- data/Gemfile +2 -3
- data/Rakefile +3 -3
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- data/lib/flipper/adapter.rb +23 -7
- data/lib/flipper/adapters/http.rb +11 -3
- data/lib/flipper/adapters/instrumented.rb +25 -2
- data/lib/flipper/adapters/memoizable.rb +19 -2
- data/lib/flipper/adapters/memory.rb +8 -4
- data/lib/flipper/adapters/operation_logger.rb +16 -3
- data/lib/flipper/dsl.rb +1 -5
- data/lib/flipper/export.rb +26 -0
- data/lib/flipper/exporter.rb +17 -0
- data/lib/flipper/exporters/json/export.rb +32 -0
- data/lib/flipper/exporters/json/v1.rb +33 -0
- data/lib/flipper/instrumentation/subscriber.rb +8 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
- data/lib/flipper/test/shared_adapter_test.rb +24 -0
- data/lib/flipper/typecast.rb +17 -0
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +2 -1
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/adapter_spec.rb +29 -2
- data/spec/flipper/adapters/http_spec.rb +25 -3
- data/spec/flipper/adapters/instrumented_spec.rb +28 -10
- data/spec/flipper/adapters/memoizable_spec.rb +30 -10
- data/spec/flipper/adapters/operation_logger_spec.rb +29 -10
- data/spec/flipper/dsl_spec.rb +20 -3
- data/spec/flipper/export_spec.rb +13 -0
- data/spec/flipper/exporter_spec.rb +16 -0
- data/spec/flipper/exporters/json/export_spec.rb +60 -0
- data/spec/flipper/exporters/json/v1_spec.rb +33 -0
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -0
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -0
- data/spec/flipper/typecast_spec.rb +79 -0
- data/spec/flipper_spec.rb +7 -1
- data/spec/support/skippable.rb +18 -0
- metadata +18 -2
@@ -0,0 +1,46 @@
|
|
1
|
+
{
|
2
|
+
"version": 1,
|
3
|
+
"features": {
|
4
|
+
"search": {
|
5
|
+
"boolean": null,
|
6
|
+
"actors": [
|
7
|
+
"john",
|
8
|
+
"another",
|
9
|
+
"testing"
|
10
|
+
],
|
11
|
+
"percentage_of_actors": null,
|
12
|
+
"percentage_of_time": null,
|
13
|
+
"groups": [
|
14
|
+
"admins"
|
15
|
+
]
|
16
|
+
},
|
17
|
+
"new_pricing": {
|
18
|
+
"boolean": "true",
|
19
|
+
"actors": [],
|
20
|
+
"percentage_of_actors": null,
|
21
|
+
"percentage_of_time": null,
|
22
|
+
"groups": []
|
23
|
+
},
|
24
|
+
"google_analytics_tag": {
|
25
|
+
"boolean": null,
|
26
|
+
"actors": [],
|
27
|
+
"percentage_of_actors": "100",
|
28
|
+
"percentage_of_time": null,
|
29
|
+
"groups": []
|
30
|
+
},
|
31
|
+
"help_scout_tag": {
|
32
|
+
"boolean": null,
|
33
|
+
"actors": [],
|
34
|
+
"percentage_of_actors": null,
|
35
|
+
"percentage_of_time": "50",
|
36
|
+
"groups": []
|
37
|
+
},
|
38
|
+
"nope": {
|
39
|
+
"boolean": null,
|
40
|
+
"actors": [],
|
41
|
+
"percentage_of_actors": null,
|
42
|
+
"percentage_of_time": null,
|
43
|
+
"groups": []
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
@@ -30,9 +30,9 @@ RSpec.describe Flipper::Adapter do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
describe '#import' do
|
33
|
-
it 'returns
|
33
|
+
it 'returns true' do
|
34
34
|
result = destination_flipper.import(source_flipper)
|
35
|
-
expect(result).to be(
|
35
|
+
expect(result).to be(true)
|
36
36
|
end
|
37
37
|
|
38
38
|
it 'can import from one adapter to another' do
|
@@ -114,5 +114,32 @@ RSpec.describe Flipper::Adapter do
|
|
114
114
|
destination_flipper.import(source_flipper)
|
115
115
|
expect(destination_flipper.features.map(&:key)).to eq([])
|
116
116
|
end
|
117
|
+
|
118
|
+
it 'can import an export' do
|
119
|
+
source_flipper.enable(:search)
|
120
|
+
source_flipper.enable(:google_analytics, Flipper::Actor.new("User;1"))
|
121
|
+
|
122
|
+
destination_flipper.import(source_flipper.export)
|
123
|
+
|
124
|
+
feature = destination_flipper[:search]
|
125
|
+
expect(feature.boolean_value).to be(true)
|
126
|
+
|
127
|
+
feature = destination_flipper[:google_analytics]
|
128
|
+
expect(feature.actors_value).to eq(Set["User;1"])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "#export" do
|
133
|
+
it "exports features" do
|
134
|
+
source_flipper.enable(:search)
|
135
|
+
export = source_flipper.export
|
136
|
+
expect(export.features.dig("search", :boolean)).to eq("true")
|
137
|
+
end
|
138
|
+
|
139
|
+
it "exports with arguments" do
|
140
|
+
source_flipper.enable(:search)
|
141
|
+
export = source_flipper.export(format: :json, version: 1)
|
142
|
+
expect(export.features.dig("search", :boolean)).to eq("true")
|
143
|
+
end
|
117
144
|
end
|
118
145
|
end
|
@@ -52,6 +52,28 @@ RSpec.describe Flipper::Adapters::Http do
|
|
52
52
|
expect(flipper[:search].disable_group(:some_made_up_group)).to be(true)
|
53
53
|
expect(flipper[:search].groups_value).to eq(Set.new)
|
54
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)
|
76
|
+
end
|
55
77
|
end
|
56
78
|
|
57
79
|
it "sends default headers" do
|
@@ -82,7 +104,7 @@ RSpec.describe Flipper::Adapters::Http do
|
|
82
104
|
|
83
105
|
describe "#get_multi" do
|
84
106
|
it "raises error when not successful response" do
|
85
|
-
stub_request(:get, "http://app.com/flipper/features?keys=feature_panel")
|
107
|
+
stub_request(:get, "http://app.com/flipper/features?keys=feature_panel&exclude_gate_names=true")
|
86
108
|
.to_return(status: 503, body: "", headers: {})
|
87
109
|
|
88
110
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
@@ -94,7 +116,7 @@ RSpec.describe Flipper::Adapters::Http do
|
|
94
116
|
|
95
117
|
describe "#get_all" do
|
96
118
|
it "raises error when not successful response" do
|
97
|
-
stub_request(:get, "http://app.com/flipper/features")
|
119
|
+
stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
|
98
120
|
.to_return(status: 503, body: "", headers: {})
|
99
121
|
|
100
122
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
@@ -106,7 +128,7 @@ RSpec.describe Flipper::Adapters::Http do
|
|
106
128
|
|
107
129
|
describe "#features" do
|
108
130
|
it "raises error when not successful response" do
|
109
|
-
stub_request(:get, "http://app.com/flipper/features")
|
131
|
+
stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
|
110
132
|
.to_return(status: 503, body: "", headers: {})
|
111
133
|
|
112
134
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
@@ -16,16 +16,6 @@ RSpec.describe Flipper::Adapters::Instrumented do
|
|
16
16
|
|
17
17
|
it_should_behave_like 'a flipper adapter'
|
18
18
|
|
19
|
-
it 'forwards missing methods to underlying adapter' do
|
20
|
-
adapter = Class.new do
|
21
|
-
def foo
|
22
|
-
:foo
|
23
|
-
end
|
24
|
-
end.new
|
25
|
-
instrumented = described_class.new(adapter)
|
26
|
-
expect(instrumented.foo).to eq(:foo)
|
27
|
-
end
|
28
|
-
|
29
19
|
describe '#name' do
|
30
20
|
it 'is instrumented' do
|
31
21
|
expect(subject.name).to be(:instrumented)
|
@@ -146,4 +136,32 @@ RSpec.describe Flipper::Adapters::Instrumented do
|
|
146
136
|
expect(event.payload[:result]).to be(result)
|
147
137
|
end
|
148
138
|
end
|
139
|
+
|
140
|
+
describe '#import' do
|
141
|
+
it 'records instrumentation' do
|
142
|
+
result = subject.import(Flipper::Adapters::Memory.new)
|
143
|
+
|
144
|
+
event = instrumenter.events.last
|
145
|
+
expect(event).not_to be_nil
|
146
|
+
expect(event.name).to eq('adapter_operation.flipper')
|
147
|
+
expect(event.payload[:operation]).to eq(:import)
|
148
|
+
expect(event.payload[:adapter_name]).to eq(:memory)
|
149
|
+
expect(event.payload[:result]).to be(result)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '#export' do
|
154
|
+
it 'records instrumentation' do
|
155
|
+
result = subject.export(format: :json, version: 1)
|
156
|
+
|
157
|
+
event = instrumenter.events.last
|
158
|
+
expect(event).not_to be_nil
|
159
|
+
expect(event.name).to eq('adapter_operation.flipper')
|
160
|
+
expect(event.payload[:operation]).to eq(:export)
|
161
|
+
expect(event.payload[:adapter_name]).to eq(:memory)
|
162
|
+
expect(event.payload[:format]).to be(:json)
|
163
|
+
expect(event.payload[:version]).to be(1)
|
164
|
+
expect(event.payload[:result]).to be(result)
|
165
|
+
end
|
166
|
+
end
|
149
167
|
end
|
@@ -11,16 +11,6 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
11
11
|
|
12
12
|
it_should_behave_like 'a flipper adapter'
|
13
13
|
|
14
|
-
it 'forwards missing methods to underlying adapter' do
|
15
|
-
adapter = Class.new do
|
16
|
-
def foo
|
17
|
-
:foo
|
18
|
-
end
|
19
|
-
end.new
|
20
|
-
memoizable = described_class.new(adapter)
|
21
|
-
expect(memoizable.foo).to eq(:foo)
|
22
|
-
end
|
23
|
-
|
24
14
|
describe '#name' do
|
25
15
|
it 'is instrumented' do
|
26
16
|
expect(subject.name).to be(:memoizable)
|
@@ -249,6 +239,36 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
249
239
|
end
|
250
240
|
end
|
251
241
|
|
242
|
+
describe "#import" do
|
243
|
+
context "with memoization enabled" do
|
244
|
+
before do
|
245
|
+
subject.memoize = true
|
246
|
+
end
|
247
|
+
|
248
|
+
it "unmemoizes features" do
|
249
|
+
cache[:foo] = "bar"
|
250
|
+
flipper[:stats].enable
|
251
|
+
flipper[:search].disable
|
252
|
+
subject.import(Flipper::Adapters::Memory.new)
|
253
|
+
expect(cache).to be_empty
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context "with memoization disabled" do
|
258
|
+
before do
|
259
|
+
subject.memoize = false
|
260
|
+
end
|
261
|
+
|
262
|
+
it "does not unmemoize features" do
|
263
|
+
cache[:foo] = "bar"
|
264
|
+
flipper[:stats].enable
|
265
|
+
flipper[:search].disable
|
266
|
+
subject.import(Flipper::Adapters::Memory.new)
|
267
|
+
expect(cache).not_to be_empty
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
252
272
|
describe '#features' do
|
253
273
|
context 'with memoization enabled' do
|
254
274
|
before do
|
@@ -18,16 +18,6 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
18
18
|
expect(output).to match(/@adapter=#<Flipper::Adapters::Memory/)
|
19
19
|
end
|
20
20
|
|
21
|
-
it 'forwards missing methods to underlying adapter' do
|
22
|
-
adapter = Class.new do
|
23
|
-
def foo
|
24
|
-
:foo
|
25
|
-
end
|
26
|
-
end.new
|
27
|
-
operation_logger = described_class.new(adapter)
|
28
|
-
expect(operation_logger.foo).to eq(:foo)
|
29
|
-
end
|
30
|
-
|
31
21
|
describe '#get' do
|
32
22
|
before do
|
33
23
|
@feature = flipper[:stats]
|
@@ -106,4 +96,33 @@ RSpec.describe Flipper::Adapters::OperationLogger do
|
|
106
96
|
expect(@result).to eq(adapter.add(@feature))
|
107
97
|
end
|
108
98
|
end
|
99
|
+
|
100
|
+
describe '#import' do
|
101
|
+
before do
|
102
|
+
@source = Flipper::Adapters::Memory.new
|
103
|
+
@result = subject.import(@source)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'logs operation' do
|
107
|
+
expect(subject.count(:import)).to be(1)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'returns result' do
|
111
|
+
expect(@result).to eq(adapter.import(@source))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#export' do
|
116
|
+
before do
|
117
|
+
@result = subject.export(format: :json, version: 1)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'logs operation' do
|
121
|
+
expect(subject.count(:export)).to be(1)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'returns result' do
|
125
|
+
expect(@result).to eq(adapter.export(format: :json, version: 1))
|
126
|
+
end
|
127
|
+
end
|
109
128
|
end
|
data/spec/flipper/dsl_spec.rb
CHANGED
@@ -342,10 +342,27 @@ RSpec.describe Flipper::DSL do
|
|
342
342
|
end
|
343
343
|
|
344
344
|
describe '#import' do
|
345
|
+
context "with flipper instance" do
|
346
|
+
it 'delegates to adapter' do
|
347
|
+
destination_flipper = build_flipper
|
348
|
+
expect(subject.adapter).to receive(:import).with(destination_flipper)
|
349
|
+
subject.import(destination_flipper)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
context "with flipper adapter" do
|
354
|
+
it 'delegates to adapter' do
|
355
|
+
destination_flipper = build_flipper
|
356
|
+
expect(subject.adapter).to receive(:import).with(destination_flipper.adapter)
|
357
|
+
subject.import(destination_flipper.adapter)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
describe "#export" do
|
345
363
|
it 'delegates to adapter' do
|
346
|
-
|
347
|
-
expect(subject.
|
348
|
-
subject.import(destination_flipper)
|
364
|
+
expect(subject.export).to eq(subject.adapter.export)
|
365
|
+
expect(subject.export(format: :json)).to eq(subject.adapter.export(format: :json))
|
349
366
|
end
|
350
367
|
end
|
351
368
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
RSpec.describe Flipper::Export do
|
2
|
+
it "can initialize" do
|
3
|
+
export = described_class.new(contents: "{}", format: :json, version: 1)
|
4
|
+
expect(export.contents).to eq("{}")
|
5
|
+
expect(export.format).to eq(:json)
|
6
|
+
expect(export.version).to eq(1)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "raises not implemented for features" do
|
10
|
+
export = described_class.new(contents: "{}", format: :json, version: 1)
|
11
|
+
expect { export.features }.to raise_error(NotImplementedError)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
RSpec.describe Flipper::Exporter do
|
2
|
+
describe ".build" do
|
3
|
+
it "builds instance of exporter" do
|
4
|
+
exporter = described_class.build(format: :json, version: 1)
|
5
|
+
expect(exporter).to be_instance_of(Flipper::Exporters::Json::V1)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "raises if format not found" do
|
9
|
+
expect { described_class.build(format: :nope, version: 1) }.to raise_error(KeyError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "raises if version not found" do
|
13
|
+
expect { described_class.build(format: :json, version: 0) }.to raise_error(KeyError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'flipper/exporters/json/v1'
|
2
|
+
|
3
|
+
RSpec.describe Flipper::Exporters::Json::Export do
|
4
|
+
let(:contents) {
|
5
|
+
<<~JSON
|
6
|
+
{
|
7
|
+
"version":1,
|
8
|
+
"features":{
|
9
|
+
"search":{"boolean":null,"groups":["admins","employees"],"actors":["User;1","User;100"],"percentage_of_actors":"10","percentage_of_time":"15"},
|
10
|
+
"plausible":{"boolean":"true","groups":[],"actors":[],"percentage_of_actors":null,"percentage_of_time":null},
|
11
|
+
"google_analytics":{"boolean":null,"groups":[],"actors":[],"percentage_of_actors":null,"percentage_of_time":null}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
JSON
|
15
|
+
}
|
16
|
+
|
17
|
+
it "can initialize" do
|
18
|
+
export = described_class.new(contents: contents)
|
19
|
+
expect(export.format).to eq(:json)
|
20
|
+
expect(export.version).to be(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can initialize with version" do
|
24
|
+
export = described_class.new(contents: contents, version: 1)
|
25
|
+
expect(export.version).to be(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can build features from contents" do
|
29
|
+
export = Flipper::Exporters::Json::Export.new(contents: contents)
|
30
|
+
expect(export.features).to eq({
|
31
|
+
"search" => {actors: Set["User;1", "User;100"], boolean: nil, groups: Set["admins", "employees"], percentage_of_actors: "10", percentage_of_time: "15"},
|
32
|
+
"plausible" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
33
|
+
"google_analytics" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
34
|
+
})
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can build an adapter from features" do
|
38
|
+
export = Flipper::Exporters::Json::Export.new(contents: contents)
|
39
|
+
expect(export.adapter).to be_instance_of(Flipper::Adapters::Memory)
|
40
|
+
expect(export.adapter.get_all).to eq({
|
41
|
+
"plausible" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
42
|
+
"search" => {actors: Set["User;1", "User;100"], boolean: nil, groups: Set["admins", "employees"], percentage_of_actors: "10", percentage_of_time: "15"},
|
43
|
+
"google_analytics" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
44
|
+
})
|
45
|
+
end
|
46
|
+
|
47
|
+
it "raises for invalid json" do
|
48
|
+
export = described_class.new(contents: "bad contents")
|
49
|
+
expect {
|
50
|
+
export.features
|
51
|
+
}.to raise_error(Flipper::Exporters::Json::JsonError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises for missing features key" do
|
55
|
+
export = described_class.new(contents: "{}")
|
56
|
+
expect {
|
57
|
+
export.features
|
58
|
+
}.to raise_error(Flipper::Exporters::Json::InvalidError)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'flipper/exporters/json/v1'
|
2
|
+
|
3
|
+
RSpec.describe Flipper::Exporters::Json::V1 do
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
it "has a version number" do
|
7
|
+
adapter = Flipper::Adapters::Memory.new
|
8
|
+
export = subject.call(adapter)
|
9
|
+
data = JSON.parse(export.contents)
|
10
|
+
expect(data["version"]).to eq(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "exports features and gates" do
|
14
|
+
adapter = Flipper::Adapters::Memory.new
|
15
|
+
flipper = Flipper.new(adapter)
|
16
|
+
flipper.enable_percentage_of_actors :search, 10
|
17
|
+
flipper.enable_percentage_of_time :search, 15
|
18
|
+
flipper.enable_actor :search, Flipper::Actor.new('User;1')
|
19
|
+
flipper.enable_actor :search, Flipper::Actor.new('User;100')
|
20
|
+
flipper.enable_group :search, :admins
|
21
|
+
flipper.enable_group :search, :employees
|
22
|
+
flipper.enable :plausible
|
23
|
+
flipper.disable :google_analytics
|
24
|
+
|
25
|
+
export = subject.call(adapter)
|
26
|
+
|
27
|
+
expect(export.features).to eq({
|
28
|
+
"google_analytics" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
29
|
+
"plausible" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
30
|
+
"search" => {actors: Set["User;1", "User;100"], boolean: nil, groups: Set["admins", "employees"], percentage_of_actors: "10", percentage_of_time: "15"},
|
31
|
+
})
|
32
|
+
end
|
33
|
+
end
|
@@ -2,6 +2,12 @@ require 'logger'
|
|
2
2
|
require 'flipper/adapters/instrumented'
|
3
3
|
require 'flipper/instrumentation/log_subscriber'
|
4
4
|
|
5
|
+
begin
|
6
|
+
require 'active_support/isolated_execution_state'
|
7
|
+
rescue LoadError
|
8
|
+
# ActiveSupport::IsolatedExecutionState is only available in Rails 5.2+
|
9
|
+
end
|
10
|
+
|
5
11
|
RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
6
12
|
let(:adapter) do
|
7
13
|
memory = Flipper::Adapters::Memory.new
|
@@ -26,6 +32,10 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
26
32
|
described_class.logger = nil
|
27
33
|
end
|
28
34
|
|
35
|
+
after(:all) do
|
36
|
+
ActiveSupport::Notifications.unsubscribe("flipper")
|
37
|
+
end
|
38
|
+
|
29
39
|
let(:log) { @io.string }
|
30
40
|
|
31
41
|
context 'feature enabled checks' do
|
@@ -2,6 +2,12 @@ require 'flipper/adapters/instrumented'
|
|
2
2
|
require 'flipper/instrumentation/statsd'
|
3
3
|
require 'statsd'
|
4
4
|
|
5
|
+
begin
|
6
|
+
require 'active_support/isolated_execution_state'
|
7
|
+
rescue LoadError
|
8
|
+
# ActiveSupport::IsolatedExecutionState is only available in Rails 5.2+
|
9
|
+
end
|
10
|
+
|
5
11
|
RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
|
6
12
|
let(:statsd_client) { Statsd.new }
|
7
13
|
let(:socket) { FakeUDPSocket.new }
|
@@ -25,6 +31,10 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
|
|
25
31
|
Thread.current[:statsd_socket] = nil
|
26
32
|
end
|
27
33
|
|
34
|
+
after(:all) do
|
35
|
+
ActiveSupport::Notifications.unsubscribe("flipper")
|
36
|
+
end
|
37
|
+
|
28
38
|
def assert_timer(metric)
|
29
39
|
regex = /#{Regexp.escape metric}\:\d+\|ms/
|
30
40
|
result = socket.buffer.detect { |op| op.first =~ regex }
|
@@ -114,4 +114,83 @@ RSpec.describe Flipper::Typecast do
|
|
114
114
|
described_class.to_set('asdf')
|
115
115
|
end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
|
116
116
|
end
|
117
|
+
|
118
|
+
describe "#features_hash" do
|
119
|
+
it "returns new hash" do
|
120
|
+
hash = {
|
121
|
+
"search" => {
|
122
|
+
boolean: nil,
|
123
|
+
}
|
124
|
+
}
|
125
|
+
result = described_class.features_hash(hash)
|
126
|
+
expect(result).not_to be(hash)
|
127
|
+
expect(result["search"]).not_to be(hash["search"])
|
128
|
+
end
|
129
|
+
|
130
|
+
it "converts gate value arrays to sets" do
|
131
|
+
hash = {
|
132
|
+
"search" => {
|
133
|
+
boolean: nil,
|
134
|
+
groups: ['a', 'b'],
|
135
|
+
actors: ['User;1'],
|
136
|
+
percentage_of_actors: nil,
|
137
|
+
percentage_of_time: nil,
|
138
|
+
},
|
139
|
+
}
|
140
|
+
result = described_class.features_hash(hash)
|
141
|
+
expect(result).to eq({
|
142
|
+
"search" => {
|
143
|
+
boolean: nil,
|
144
|
+
groups: Set['a', 'b'],
|
145
|
+
actors: Set['User;1'],
|
146
|
+
percentage_of_actors: nil,
|
147
|
+
percentage_of_time: nil,
|
148
|
+
},
|
149
|
+
})
|
150
|
+
end
|
151
|
+
|
152
|
+
it "converts gate value boolean and integers to strings" do
|
153
|
+
hash = {
|
154
|
+
"search" => {
|
155
|
+
boolean: true,
|
156
|
+
groups: Set.new,
|
157
|
+
actors: Set.new,
|
158
|
+
percentage_of_actors: 10,
|
159
|
+
percentage_of_time: 15,
|
160
|
+
},
|
161
|
+
}
|
162
|
+
result = described_class.features_hash(hash)
|
163
|
+
expect(result).to eq({
|
164
|
+
"search" => {
|
165
|
+
boolean: "true",
|
166
|
+
groups: Set.new,
|
167
|
+
actors: Set.new,
|
168
|
+
percentage_of_actors: "10",
|
169
|
+
percentage_of_time: "15",
|
170
|
+
},
|
171
|
+
})
|
172
|
+
end
|
173
|
+
|
174
|
+
it "converts string gate keys to symbols" do
|
175
|
+
hash = {
|
176
|
+
"search" => {
|
177
|
+
"boolean" => nil,
|
178
|
+
"groups" => Set.new,
|
179
|
+
"actors" => Set.new,
|
180
|
+
"percentage_of_actors" => nil,
|
181
|
+
"percentage_of_time" => nil,
|
182
|
+
},
|
183
|
+
}
|
184
|
+
result = described_class.features_hash(hash)
|
185
|
+
expect(result).to eq({
|
186
|
+
"search" => {
|
187
|
+
boolean: nil,
|
188
|
+
groups: Set.new,
|
189
|
+
actors: Set.new,
|
190
|
+
percentage_of_actors: nil,
|
191
|
+
percentage_of_time: nil,
|
192
|
+
},
|
193
|
+
})
|
194
|
+
end
|
195
|
+
end
|
117
196
|
end
|
data/spec/flipper_spec.rb
CHANGED
@@ -202,6 +202,12 @@ RSpec.describe Flipper do
|
|
202
202
|
expect(described_class.enabled?(:search)).to be(true)
|
203
203
|
end
|
204
204
|
|
205
|
+
it 'delegates export to instance' do
|
206
|
+
described_class.enable(:search)
|
207
|
+
expect(described_class.export).to eq(described_class.adapter.export)
|
208
|
+
expect(described_class.export(format: :json)).to eq(described_class.adapter.export(format: :json))
|
209
|
+
end
|
210
|
+
|
205
211
|
it 'delegates adapter to instance' do
|
206
212
|
expect(described_class.adapter).to eq(described_class.instance.adapter)
|
207
213
|
end
|
@@ -222,7 +228,7 @@ RSpec.describe Flipper do
|
|
222
228
|
end
|
223
229
|
|
224
230
|
it 'delegates sync stuff to instance for Flipper::Cloud' do
|
225
|
-
stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").
|
231
|
+
stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
|
226
232
|
with({
|
227
233
|
headers: {
|
228
234
|
'Flipper-Cloud-Token'=>'asdf',
|
@@ -0,0 +1,18 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.before(:all) do
|
3
|
+
$skip = false
|
4
|
+
end
|
5
|
+
|
6
|
+
def skip_on_error(error, message, &block)
|
7
|
+
# Premptively skip if we've already skipped
|
8
|
+
skip(message) if $skip
|
9
|
+
block.call
|
10
|
+
rescue error
|
11
|
+
if ENV["CI"]
|
12
|
+
raise
|
13
|
+
else
|
14
|
+
$skip = true
|
15
|
+
skip(message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|