flipper 0.26.2 → 0.27.1
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/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
|