flipper 0.22.2 → 0.23.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 +6 -1
- data/.github/workflows/examples.yml +1 -0
- data/.rspec +1 -0
- data/Changelog.md +18 -2
- data/Gemfile +2 -2
- data/README.md +15 -67
- data/docs/README.md +1 -0
- data/docs/images/banner.jpg +0 -0
- data/lib/flipper/adapters/dual_write.rb +9 -15
- data/lib/flipper/adapters/failover.rb +83 -0
- data/lib/flipper/adapters/http/client.rb +11 -1
- data/lib/flipper/adapters/http/error.rb +19 -1
- data/lib/flipper/adapters/memoizable.rb +8 -16
- data/lib/flipper/adapters/pstore.rb +2 -5
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
- data/lib/flipper/adapters/sync.rb +9 -15
- data/lib/flipper/instrumenters/memory.rb +6 -2
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/actor_spec.rb +0 -2
- data/spec/flipper/adapter_spec.rb +0 -2
- data/spec/flipper/adapters/dual_write_spec.rb +0 -2
- data/spec/flipper/adapters/failover_spec.rb +129 -0
- data/spec/flipper/adapters/http_spec.rb +39 -3
- data/spec/flipper/adapters/instrumented_spec.rb +0 -2
- data/spec/flipper/adapters/memoizable_spec.rb +0 -2
- data/spec/flipper/adapters/memory_spec.rb +0 -3
- data/spec/flipper/adapters/operation_logger_spec.rb +0 -2
- data/spec/flipper/adapters/pstore_spec.rb +0 -2
- data/spec/flipper/adapters/read_only_spec.rb +0 -1
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync_spec.rb +0 -2
- data/spec/flipper/configuration_spec.rb +0 -1
- data/spec/flipper/dsl_spec.rb +0 -1
- data/spec/flipper/feature_check_context_spec.rb +0 -2
- data/spec/flipper/feature_spec.rb +0 -1
- data/spec/flipper/gate_spec.rb +0 -2
- data/spec/flipper/gate_values_spec.rb +0 -1
- data/spec/flipper/gates/actor_spec.rb +0 -2
- data/spec/flipper/gates/boolean_spec.rb +0 -2
- data/spec/flipper/gates/group_spec.rb +0 -2
- data/spec/flipper/gates/percentage_of_actors_spec.rb +0 -2
- data/spec/flipper/gates/percentage_of_time_spec.rb +0 -2
- data/spec/flipper/identifier_spec.rb +0 -1
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +0 -1
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +0 -1
- data/spec/flipper/instrumenters/memory_spec.rb +18 -1
- data/spec/flipper/instrumenters/noop_spec.rb +14 -8
- data/spec/flipper/middleware/memoizer_spec.rb +0 -2
- data/spec/flipper/middleware/setup_env_spec.rb +0 -2
- data/spec/flipper/railtie_spec.rb +17 -19
- data/spec/flipper/registry_spec.rb +0 -1
- data/spec/flipper/typecast_spec.rb +0 -1
- data/spec/flipper/types/actor_spec.rb +0 -1
- data/spec/flipper/types/boolean_spec.rb +0 -1
- data/spec/flipper/types/group_spec.rb +0 -1
- data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
- data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
- data/spec/flipper/types/percentage_spec.rb +0 -1
- data/spec/flipper_integration_spec.rb +0 -1
- data/spec/flipper_spec.rb +0 -1
- data/spec/{helper.rb → spec_helper.rb} +2 -1
- data/spec/support/spec_helpers.rb +2 -6
- metadata +10 -13
- data/docs/Adapters.md +0 -124
- data/docs/Caveats.md +0 -4
- data/docs/Gates.md +0 -167
- data/docs/Instrumentation.md +0 -27
- data/docs/Optimization.md +0 -137
- data/docs/api/README.md +0 -884
- data/docs/http/README.md +0 -36
- data/docs/read-only/README.md +0 -24
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require 'dalli'
|
|
2
|
+
require 'flipper/adapters/failover'
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'pstore'
|
|
5
|
+
require 'redis'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Flipper::Adapters::Failover do
|
|
8
|
+
subject { described_class.new(primary, secondary, options) }
|
|
9
|
+
|
|
10
|
+
let(:primary) { Flipper::Adapters::Memory.new }
|
|
11
|
+
let(:secondary) { Flipper::Adapters::Memory.new }
|
|
12
|
+
let(:options) { {} }
|
|
13
|
+
let(:flipper) { Flipper.new(subject) }
|
|
14
|
+
|
|
15
|
+
context 'when the primary is a functioning adapter' do
|
|
16
|
+
it_should_behave_like 'a flipper adapter'
|
|
17
|
+
|
|
18
|
+
it 'should not call the secondary' do
|
|
19
|
+
expect(secondary).not_to receive(:features)
|
|
20
|
+
subject.features
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'should not write to secondary' do
|
|
24
|
+
expect(secondary).not_to receive(:add)
|
|
25
|
+
expect(secondary).not_to receive(:enable)
|
|
26
|
+
|
|
27
|
+
flipper[:flag].enable
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'when dual_write is enabled' do
|
|
31
|
+
let(:options) { { dual_write: true } }
|
|
32
|
+
|
|
33
|
+
it_should_behave_like 'a flipper adapter'
|
|
34
|
+
|
|
35
|
+
it 'writes to both primary and secondary' do
|
|
36
|
+
expect(primary).to receive(:add).and_call_original
|
|
37
|
+
expect(primary).to receive(:enable).and_call_original
|
|
38
|
+
|
|
39
|
+
expect(secondary).to receive(:add)
|
|
40
|
+
expect(secondary).to receive(:enable)
|
|
41
|
+
|
|
42
|
+
flipper[:flag].enable
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'when primary fails during read operations' do
|
|
48
|
+
before do
|
|
49
|
+
allow(primary).to receive(:features).and_raise(Redis::ConnectionError)
|
|
50
|
+
allow(primary).to receive(:get).and_raise(Dalli::NetworkError)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'fails over to the secondary adapter for reads' do
|
|
54
|
+
expect(secondary).to receive(:features)
|
|
55
|
+
subject.features
|
|
56
|
+
|
|
57
|
+
flipper[:flag].enable
|
|
58
|
+
expect(secondary).to receive(:get).and_call_original
|
|
59
|
+
flipper[:flag].enabled?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'when dual_write is enabled' do
|
|
63
|
+
let(:options) { { dual_write: true } }
|
|
64
|
+
|
|
65
|
+
it_should_behave_like 'a flipper adapter'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when primary fails during write operations' do
|
|
70
|
+
before do
|
|
71
|
+
allow(primary).to receive(:add).and_raise(PStore::Error)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
let(:options) { { dual_write: true } }
|
|
75
|
+
|
|
76
|
+
it 'fails and does not write to secondary adapter' do
|
|
77
|
+
expect(secondary).not_to receive(:add)
|
|
78
|
+
expect(secondary).not_to receive(:enable)
|
|
79
|
+
|
|
80
|
+
expect { flipper[:flag].enable }.to raise_error(PStore::Error)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context 'when primary is instrumented and fails' do
|
|
85
|
+
before do
|
|
86
|
+
allow(memory_adapter).to receive(:get).and_raise(Net::ReadTimeout)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
let(:memory_adapter) { Flipper::Adapters::Memory.new }
|
|
90
|
+
let(:primary) do
|
|
91
|
+
Flipper::Adapters::Instrumented.new(
|
|
92
|
+
memory_adapter,
|
|
93
|
+
instrumenter: instrumenter,
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
|
97
|
+
|
|
98
|
+
it 'logs the raised exception' do
|
|
99
|
+
flipper[:flag].enabled?
|
|
100
|
+
|
|
101
|
+
expect(instrumenter.events.count).to be 1
|
|
102
|
+
|
|
103
|
+
payload = instrumenter.events[0].payload
|
|
104
|
+
expect(payload.keys).to include(:exception, :exception_object)
|
|
105
|
+
expect(payload[:exception_object]).to be_a Net::ReadTimeout
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'when adapter raises a SyntaxError' do
|
|
110
|
+
before do
|
|
111
|
+
allow(primary).to receive(:features).and_raise(SyntaxError)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'does not rescue this type by default' do
|
|
115
|
+
expect {
|
|
116
|
+
subject.features
|
|
117
|
+
}.to raise_error(SyntaxError)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context 'when Failover adapter is configured to catch SyntaxError' do
|
|
121
|
+
let(:options) { { errors: [ SyntaxError ] } }
|
|
122
|
+
|
|
123
|
+
it 'fails over to secondary adapter' do
|
|
124
|
+
expect(secondary).to receive(:features)
|
|
125
|
+
subject.features
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
require 'helper'
|
|
2
1
|
require 'flipper/adapters/http'
|
|
3
2
|
require 'flipper/adapters/pstore'
|
|
4
|
-
require 'flipper/spec/shared_adapter_specs'
|
|
5
3
|
require 'rack/handler/webrick'
|
|
6
4
|
|
|
7
5
|
FLIPPER_SPEC_API_PORT = ENV.fetch('FLIPPER_SPEC_API_PORT', 9001).to_i
|
|
@@ -159,6 +157,19 @@ RSpec.describe Flipper::Adapters::Http do
|
|
|
159
157
|
stub_request(:post, /app.com/)
|
|
160
158
|
.to_return(status: 503, body: "{}", headers: {})
|
|
161
159
|
|
|
160
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
|
161
|
+
feature = Flipper::Feature.new(:search, adapter)
|
|
162
|
+
gate = feature.gate(:boolean)
|
|
163
|
+
thing = gate.wrap(true)
|
|
164
|
+
expect {
|
|
165
|
+
adapter.enable(feature, gate, thing)
|
|
166
|
+
}.to raise_error(Flipper::Adapters::Http::Error, "Failed with status: 503")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "doesn't raise json error if body cannot be parsed" do
|
|
170
|
+
stub_request(:post, /app.com/)
|
|
171
|
+
.to_return(status: 503, body: "barf", headers: {})
|
|
172
|
+
|
|
162
173
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
|
163
174
|
feature = Flipper::Feature.new(:search, adapter)
|
|
164
175
|
gate = feature.gate(:boolean)
|
|
@@ -167,6 +178,30 @@ RSpec.describe Flipper::Adapters::Http do
|
|
|
167
178
|
adapter.enable(feature, gate, thing)
|
|
168
179
|
}.to raise_error(Flipper::Adapters::Http::Error)
|
|
169
180
|
end
|
|
181
|
+
|
|
182
|
+
it "includes response information if available when raising error" do
|
|
183
|
+
api_response = {
|
|
184
|
+
"code" => "error",
|
|
185
|
+
"message" => "This feature has reached the limit to the number of " +
|
|
186
|
+
"actors per feature. Check out groups as a more flexible " +
|
|
187
|
+
"way to enable many actors.",
|
|
188
|
+
"more_info" => "https://www.flippercloud.io/docs",
|
|
189
|
+
}
|
|
190
|
+
stub_request(:post, /app.com/)
|
|
191
|
+
.to_return(status: 503, body: JSON.dump(api_response), headers: {})
|
|
192
|
+
|
|
193
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
|
194
|
+
feature = Flipper::Feature.new(:search, adapter)
|
|
195
|
+
gate = feature.gate(:boolean)
|
|
196
|
+
thing = gate.wrap(true)
|
|
197
|
+
error_message = "Failed with status: 503\n\nThis feature has reached the " +
|
|
198
|
+
"limit to the number of actors per feature. Check out " +
|
|
199
|
+
"groups as a more flexible way to enable many actors.\n" +
|
|
200
|
+
"https://www.flippercloud.io/docs"
|
|
201
|
+
expect {
|
|
202
|
+
adapter.enable(feature, gate, thing)
|
|
203
|
+
}.to raise_error(Flipper::Adapters::Http::Error, error_message)
|
|
204
|
+
end
|
|
170
205
|
end
|
|
171
206
|
|
|
172
207
|
describe "#disable" do
|
|
@@ -202,7 +237,8 @@ RSpec.describe Flipper::Adapters::Http do
|
|
|
202
237
|
let(:feature) { flipper[:feature_panel] }
|
|
203
238
|
|
|
204
239
|
before do
|
|
205
|
-
stub_request(:get, %r{\Ahttp://app.com*}).
|
|
240
|
+
stub_request(:get, %r{\Ahttp://app.com*}).
|
|
241
|
+
to_return(body: fixture_file('feature.json'))
|
|
206
242
|
end
|
|
207
243
|
|
|
208
244
|
it 'allows client to set request headers' do
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
require "helper"
|
|
2
1
|
require "flipper/adapters/sync/interval_synchronizer"
|
|
3
2
|
|
|
4
3
|
RSpec.describe Flipper::Adapters::Sync::IntervalSynchronizer do
|
|
5
4
|
let(:events) { [] }
|
|
6
|
-
let(:synchronizer) { -> { events <<
|
|
5
|
+
let(:synchronizer) { -> { events << now } }
|
|
7
6
|
let(:interval) { 10 }
|
|
7
|
+
let(:now) { subject.send(:now) }
|
|
8
8
|
|
|
9
9
|
subject { described_class.new(synchronizer, interval: interval) }
|
|
10
10
|
|
|
@@ -15,19 +15,18 @@ RSpec.describe Flipper::Adapters::Sync::IntervalSynchronizer do
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
it "only invokes wrapped synchronizer every interval seconds" do
|
|
18
|
-
now = described_class.now
|
|
19
18
|
subject.call
|
|
20
19
|
events.clear
|
|
21
20
|
|
|
22
21
|
# move time to one millisecond less than last sync + interval
|
|
23
22
|
1.upto(interval) do |i|
|
|
24
|
-
allow(
|
|
23
|
+
allow(subject).to receive(:now).and_return(now + i - 1)
|
|
25
24
|
subject.call
|
|
26
25
|
end
|
|
27
26
|
expect(events.size).to be(0)
|
|
28
27
|
|
|
29
28
|
# move time to last sync + interval in milliseconds
|
|
30
|
-
allow(
|
|
29
|
+
allow(subject).to receive(:now).and_return(now + interval)
|
|
31
30
|
subject.call
|
|
32
31
|
expect(events.size).to be(1)
|
|
33
32
|
end
|
data/spec/flipper/dsl_spec.rb
CHANGED
data/spec/flipper/gate_spec.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require 'helper'
|
|
2
1
|
require 'flipper/instrumenters/memory'
|
|
3
2
|
|
|
4
3
|
RSpec.describe Flipper::Instrumenters::Memory do
|
|
@@ -22,5 +21,23 @@ RSpec.describe Flipper::Instrumenters::Memory do
|
|
|
22
21
|
event = described_class::Event.new(name, payload, block_result)
|
|
23
22
|
expect(instrumenter.events).to eq([event])
|
|
24
23
|
end
|
|
24
|
+
|
|
25
|
+
context 'when an error is raised' do
|
|
26
|
+
subject do
|
|
27
|
+
instrumenter.instrument(:name) { raise IOError }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:instrumenter) { described_class.new }
|
|
31
|
+
|
|
32
|
+
it 'captures and propagates the error' do
|
|
33
|
+
expect { subject }.to raise_error(IOError)
|
|
34
|
+
|
|
35
|
+
expect(instrumenter.events.count).to be 1
|
|
36
|
+
|
|
37
|
+
payload = instrumenter.events[0].payload
|
|
38
|
+
expect(payload.keys).to include(:exception, :exception_object)
|
|
39
|
+
expect(payload[:exception_object]).to be_a IOError
|
|
40
|
+
end
|
|
41
|
+
end
|
|
25
42
|
end
|
|
26
43
|
end
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
require 'helper'
|
|
2
|
-
|
|
3
1
|
RSpec.describe Flipper::Instrumenters::Noop do
|
|
4
2
|
describe '.instrument' do
|
|
5
3
|
context 'with name' do
|
|
6
4
|
it 'yields block' do
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
expect { |block|
|
|
6
|
+
described_class.instrument(:foo, &block)
|
|
7
|
+
}.to yield_control
|
|
10
8
|
end
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
context 'with name and payload' do
|
|
12
|
+
let(:payload) { { pay: :load } }
|
|
13
|
+
|
|
14
14
|
it 'yields block' do
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
expect { |block|
|
|
16
|
+
described_class.instrument(:foo, payload, &block)
|
|
17
|
+
}.to yield_control
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'yields the payload' do
|
|
21
|
+
described_class.instrument(:foo, payload) do |block_payload|
|
|
22
|
+
expect(block_payload).to eq payload
|
|
23
|
+
end
|
|
18
24
|
end
|
|
19
25
|
end
|
|
20
26
|
end
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require 'helper'
|
|
2
1
|
require 'rack/test'
|
|
3
2
|
require 'active_support/cache'
|
|
4
3
|
require 'active_support/cache/dalli_store'
|
|
@@ -362,7 +361,6 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
|
362
361
|
end.to_app
|
|
363
362
|
end
|
|
364
363
|
|
|
365
|
-
|
|
366
364
|
context 'and unless option' do
|
|
367
365
|
before do
|
|
368
366
|
options[:unless] = ->(request) { request.path.start_with?("/assets") }
|
|
@@ -1,32 +1,30 @@
|
|
|
1
|
-
require 'helper'
|
|
2
1
|
require 'rails'
|
|
3
2
|
require 'flipper/railtie'
|
|
4
3
|
|
|
5
4
|
RSpec.describe Flipper::Railtie do
|
|
6
5
|
let(:application) do
|
|
7
|
-
|
|
6
|
+
Class.new(Rails::Application).new(
|
|
8
7
|
railties: [Flipper::Railtie],
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
app.run_load_hooks!
|
|
8
|
+
).tap do |app|
|
|
9
|
+
app.config.eager_load = false
|
|
10
|
+
app.run_load_hooks!
|
|
11
|
+
end
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
before do
|
|
17
|
-
|
|
15
|
+
ActiveSupport::Dependencies.autoload_paths = ActiveSupport::Dependencies.autoload_paths.dup
|
|
16
|
+
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_once_paths.dup
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
19
|
+
let(:config) { application.config.flipper }
|
|
20
|
+
|
|
21
|
+
subject { application.initialize! }
|
|
24
22
|
|
|
25
23
|
describe 'initializers' do
|
|
26
24
|
it 'sets defaults' do
|
|
27
|
-
expect(
|
|
28
|
-
expect(
|
|
29
|
-
expect(
|
|
25
|
+
expect(config.env_key).to eq("flipper")
|
|
26
|
+
expect(config.memoize).to be(true)
|
|
27
|
+
expect(config.preload).to be(true)
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
it "configures instrumentor on default instance" do
|
|
@@ -36,19 +34,19 @@ RSpec.describe Flipper::Railtie do
|
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
it 'uses Memoizer middleware if config.memoize = true' do
|
|
39
|
-
expect(subject.middleware
|
|
37
|
+
expect(subject.middleware).to include(Flipper::Middleware::Memoizer)
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
it 'does not use Memoizer middleware if config.memoize = false' do
|
|
43
41
|
# load but don't initialize
|
|
44
|
-
|
|
42
|
+
config.memoize = false
|
|
45
43
|
|
|
46
|
-
expect(subject.middleware
|
|
44
|
+
expect(subject.middleware).not_to include(Flipper::Middleware::Memoizer)
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
it 'passes config to memoizer' do
|
|
50
48
|
# load but don't initialize
|
|
51
|
-
|
|
49
|
+
config.update(
|
|
52
50
|
env_key: 'my_flipper',
|
|
53
51
|
preload: [:stats, :search]
|
|
54
52
|
)
|
data/spec/flipper_spec.rb
CHANGED
|
@@ -14,8 +14,9 @@ require 'webmock/rspec'
|
|
|
14
14
|
WebMock.disable_net_connect!(allow_localhost: true)
|
|
15
15
|
|
|
16
16
|
require 'flipper'
|
|
17
|
-
require 'flipper/ui'
|
|
18
17
|
require 'flipper/api'
|
|
18
|
+
require 'flipper/spec/shared_adapter_specs'
|
|
19
|
+
require 'flipper/ui'
|
|
19
20
|
|
|
20
21
|
Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }
|
|
21
22
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'ice_age'
|
|
2
2
|
require 'json'
|
|
3
3
|
require 'rack/test'
|
|
4
4
|
|
|
@@ -31,7 +31,7 @@ module SpecHelpers
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def api_error_code_reference_url
|
|
34
|
-
'https://
|
|
34
|
+
'https://flippercloud.io/docs/api#error-code-reference'
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def api_not_found_response
|
|
@@ -58,10 +58,6 @@ module SpecHelpers
|
|
|
58
58
|
}
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def with_modified_env(options, &block)
|
|
62
|
-
ClimateControl.modify(options, &block)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
61
|
def silence
|
|
66
62
|
# Store the original stderr and stdout in order to restore them later
|
|
67
63
|
original_stderr = $stderr
|