stoplight 3.0.2 → 4.0.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/README.md +176 -180
- data/lib/stoplight/builder.rb +68 -0
- data/lib/stoplight/circuit_breaker.rb +92 -0
- data/lib/stoplight/configurable.rb +95 -0
- data/lib/stoplight/configuration.rb +126 -0
- data/lib/stoplight/data_store/memory.rb +20 -5
- data/lib/stoplight/data_store/redis.rb +37 -5
- data/lib/stoplight/default.rb +2 -0
- data/lib/stoplight/error.rb +1 -0
- data/lib/stoplight/light/deprecated.rb +44 -0
- data/lib/stoplight/light/lockable.rb +45 -0
- data/lib/stoplight/light/runnable.rb +25 -24
- data/lib/stoplight/light.rb +69 -63
- data/lib/stoplight/rspec/generic_notifier.rb +42 -0
- data/lib/stoplight/rspec.rb +3 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight.rb +32 -8
- data/spec/spec_helper.rb +7 -0
- data/spec/stoplight/builder_spec.rb +165 -0
- data/spec/stoplight/circuit_breaker_spec.rb +35 -0
- data/spec/stoplight/configurable_spec.rb +25 -0
- data/spec/stoplight/data_store/memory_spec.rb +12 -149
- data/spec/stoplight/data_store/redis_spec.rb +26 -158
- data/spec/stoplight/error_spec.rb +10 -0
- data/spec/stoplight/light/lockable_spec.rb +93 -0
- data/spec/stoplight/light/runnable_spec.rb +12 -273
- data/spec/stoplight/light_spec.rb +4 -28
- data/spec/stoplight/notifier/generic_spec.rb +35 -35
- data/spec/stoplight/notifier/io_spec.rb +1 -0
- data/spec/stoplight/notifier/logger_spec.rb +3 -0
- data/spec/stoplight_spec.rb +17 -6
- data/spec/support/configurable.rb +69 -0
- data/spec/support/data_store/base/clear_failures.rb +18 -0
- data/spec/support/data_store/base/clear_state.rb +20 -0
- data/spec/support/data_store/base/get_all.rb +44 -0
- data/spec/support/data_store/base/get_failures.rb +30 -0
- data/spec/support/data_store/base/get_state.rb +7 -0
- data/spec/support/data_store/base/names.rb +29 -0
- data/spec/support/data_store/base/record_failures.rb +70 -0
- data/spec/support/data_store/base/set_state.rb +15 -0
- data/spec/support/data_store/base/with_notification_lock.rb +27 -0
- data/spec/support/data_store/base.rb +21 -0
- data/spec/support/database_cleaner.rb +26 -0
- data/spec/support/exception_helpers.rb +9 -0
- data/spec/support/light/runnable/color.rb +79 -0
- data/spec/support/light/runnable/run.rb +247 -0
- data/spec/support/light/runnable.rb +4 -0
- metadata +51 -231
- data/lib/stoplight/notifier/bugsnag.rb +0 -37
- data/lib/stoplight/notifier/honeybadger.rb +0 -44
- data/lib/stoplight/notifier/pagerduty.rb +0 -21
- data/lib/stoplight/notifier/raven.rb +0 -40
- data/lib/stoplight/notifier/rollbar.rb +0 -39
- data/lib/stoplight/notifier/slack.rb +0 -21
- data/spec/stoplight/notifier/bugsnag_spec.rb +0 -90
- data/spec/stoplight/notifier/honeybadger_spec.rb +0 -88
- data/spec/stoplight/notifier/pagerduty_spec.rb +0 -40
- data/spec/stoplight/notifier/raven_spec.rb +0 -90
- data/spec/stoplight/notifier/rollbar_spec.rb +0 -90
- data/spec/stoplight/notifier/slack_spec.rb +0 -46
@@ -3,15 +3,7 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
require 'stringio'
|
5
5
|
|
6
|
-
RSpec.describe Stoplight::Light::Runnable do
|
7
|
-
subject { Stoplight::Light.new(name, &code) }
|
8
|
-
|
9
|
-
let(:code) { -> { code_result } }
|
10
|
-
let(:code_result) { random_string }
|
11
|
-
let(:fallback) { ->(_) { fallback_result } }
|
12
|
-
let(:fallback_result) { random_string }
|
13
|
-
let(:name) { random_string }
|
14
|
-
|
6
|
+
RSpec.describe Stoplight::Light::Runnable, :redis do
|
15
7
|
let(:failure) do
|
16
8
|
Stoplight::Failure.new(error.class.name, error.message, time)
|
17
9
|
end
|
@@ -24,274 +16,21 @@ RSpec.describe Stoplight::Light::Runnable do
|
|
24
16
|
('a'..'z').to_a.sample(8).join
|
25
17
|
end
|
26
18
|
|
27
|
-
|
28
|
-
|
29
|
-
expect(subject.color).to eql(Stoplight::Color::GREEN)
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'is green when locked green' do
|
33
|
-
subject.data_store.set_state(subject, Stoplight::State::LOCKED_GREEN)
|
34
|
-
expect(subject.color).to eql(Stoplight::Color::GREEN)
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'is red when locked red' do
|
38
|
-
subject.data_store.set_state(subject, Stoplight::State::LOCKED_RED)
|
39
|
-
expect(subject.color).to eql(Stoplight::Color::RED)
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'is red when there are many failures' do
|
43
|
-
subject.threshold.times do
|
44
|
-
subject.data_store.record_failure(subject, failure)
|
45
|
-
end
|
46
|
-
expect(subject.color).to eql(Stoplight::Color::RED)
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'is yellow when the most recent failure is old' do
|
50
|
-
(subject.threshold - 1).times do
|
51
|
-
subject.data_store.record_failure(subject, failure)
|
52
|
-
end
|
53
|
-
other = Stoplight::Failure.new(
|
54
|
-
error.class.name, error.message, Time.new - subject.cool_off_time
|
55
|
-
)
|
56
|
-
subject.data_store.record_failure(subject, other)
|
57
|
-
expect(subject.color).to eql(Stoplight::Color::YELLOW)
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'is red when the least recent failure is old' do
|
61
|
-
other = Stoplight::Failure.new(
|
62
|
-
error.class.name, error.message, Time.new - subject.cool_off_time
|
63
|
-
)
|
64
|
-
subject.data_store.record_failure(subject, other)
|
65
|
-
(subject.threshold - 1).times do
|
66
|
-
subject.data_store.record_failure(subject, failure)
|
67
|
-
end
|
68
|
-
expect(subject.color).to eql(Stoplight::Color::RED)
|
69
|
-
end
|
19
|
+
before do
|
20
|
+
light.with_data_store(data_store)
|
70
21
|
end
|
71
22
|
|
72
|
-
|
73
|
-
let(:
|
74
|
-
let(:notifier) { Stoplight::Notifier::IO.new(io) }
|
75
|
-
let(:io) { StringIO.new }
|
76
|
-
|
77
|
-
before { subject.with_notifiers(notifiers) }
|
78
|
-
|
79
|
-
context 'when the light is green' do
|
80
|
-
before { subject.data_store.clear_failures(subject) }
|
81
|
-
|
82
|
-
it 'runs the code' do
|
83
|
-
expect(subject.run).to eql(code_result)
|
84
|
-
end
|
85
|
-
|
86
|
-
context 'with some failures' do
|
87
|
-
before { subject.data_store.record_failure(subject, failure) }
|
88
|
-
|
89
|
-
it 'clears the failures' do
|
90
|
-
subject.run
|
91
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context 'when the code is failing' do
|
96
|
-
let(:code_result) { raise error }
|
97
|
-
|
98
|
-
it 're-raises the error' do
|
99
|
-
expect { subject.run }.to raise_error(error.class)
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'records the failure' do
|
103
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
104
|
-
begin
|
105
|
-
subject.run
|
106
|
-
rescue error.class
|
107
|
-
nil
|
108
|
-
end
|
109
|
-
expect(subject.data_store.get_failures(subject).size).to eql(1)
|
110
|
-
end
|
111
|
-
|
112
|
-
context 'when we did not send notifications yet' do
|
113
|
-
it 'notifies when transitioning to red' do
|
114
|
-
subject.threshold.times do
|
115
|
-
expect(io.string).to eql('')
|
116
|
-
begin
|
117
|
-
subject.run
|
118
|
-
rescue error.class
|
119
|
-
nil
|
120
|
-
end
|
121
|
-
end
|
122
|
-
expect(io.string).to_not eql('')
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
context 'when we already sent notifications' do
|
127
|
-
before do
|
128
|
-
subject.data_store.with_notification_lock(subject, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
|
129
|
-
end
|
130
|
-
|
131
|
-
it 'does not send new notifications' do
|
132
|
-
subject.threshold.times do
|
133
|
-
expect(io.string).to eql('')
|
134
|
-
begin
|
135
|
-
subject.run
|
136
|
-
rescue error.class
|
137
|
-
nil
|
138
|
-
end
|
139
|
-
end
|
140
|
-
expect(io.string).to eql('')
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
it 'notifies when transitioning to red' do
|
145
|
-
subject.threshold.times do
|
146
|
-
expect(io.string).to eql('')
|
147
|
-
begin
|
148
|
-
subject.run
|
149
|
-
rescue error.class
|
150
|
-
nil
|
151
|
-
end
|
152
|
-
end
|
153
|
-
expect(io.string).to_not eql('')
|
154
|
-
end
|
155
|
-
|
156
|
-
context 'with an error handler' do
|
157
|
-
let(:result) do
|
158
|
-
subject.run
|
159
|
-
expect(false).to be(true)
|
160
|
-
rescue error.class
|
161
|
-
expect(true).to be(true)
|
162
|
-
end
|
23
|
+
context 'with memory data store' do
|
24
|
+
let(:data_store) { Stoplight::DataStore::Memory.new }
|
163
25
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
.to change { subject.data_store.get_failures(subject).size }
|
168
|
-
.by(1)
|
169
|
-
end
|
170
|
-
|
171
|
-
it 'records the failure when the handler calls handle' do
|
172
|
-
subject.with_error_handler { |error, handle| handle.call(error) }
|
173
|
-
expect { result }
|
174
|
-
.to change { subject.data_store.get_failures(subject).size }
|
175
|
-
.by(1)
|
176
|
-
end
|
177
|
-
|
178
|
-
it 'does not record the failure when the handler raises' do
|
179
|
-
subject.with_error_handler { |error, _handle| raise error }
|
180
|
-
expect { result }
|
181
|
-
.to_not change { subject.data_store.get_failures(subject).size }
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
context 'with a fallback' do
|
186
|
-
before { subject.with_fallback(&fallback) }
|
187
|
-
|
188
|
-
it 'runs the fallback' do
|
189
|
-
expect(subject.run).to eql(fallback_result)
|
190
|
-
end
|
191
|
-
|
192
|
-
it 'passes the error to the fallback' do
|
193
|
-
subject.with_fallback do |e|
|
194
|
-
expect(e).to eql(error)
|
195
|
-
fallback_result
|
196
|
-
end
|
197
|
-
expect(subject.run).to eql(fallback_result)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
context 'when the data store is failing' do
|
203
|
-
let(:data_store) { Object.new }
|
204
|
-
let(:error_notifier) { ->(_) {} }
|
205
|
-
|
206
|
-
before do
|
207
|
-
subject
|
208
|
-
.with_data_store(data_store)
|
209
|
-
.with_error_notifier(&error_notifier)
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'runs the code' do
|
213
|
-
expect(subject.run).to eql(code_result)
|
214
|
-
end
|
215
|
-
|
216
|
-
it 'notifies about the error' do
|
217
|
-
has_notified = false
|
218
|
-
subject.with_error_notifier do |e|
|
219
|
-
has_notified = true
|
220
|
-
expect(e).to be_a(NoMethodError)
|
221
|
-
end
|
222
|
-
subject.run
|
223
|
-
expect(has_notified).to eql(true)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
context 'when the light is yellow' do
|
229
|
-
before do
|
230
|
-
(subject.threshold - 1).times do
|
231
|
-
subject.data_store.record_failure(subject, failure)
|
232
|
-
end
|
233
|
-
|
234
|
-
other = Stoplight::Failure.new(
|
235
|
-
error.class.name, error.message, time - subject.cool_off_time
|
236
|
-
)
|
237
|
-
subject.data_store.record_failure(subject, other)
|
238
|
-
end
|
239
|
-
|
240
|
-
it 'runs the code' do
|
241
|
-
expect(subject.run).to eql(code_result)
|
242
|
-
end
|
243
|
-
|
244
|
-
it 'notifies when transitioning to green' do
|
245
|
-
expect(io.string).to eql('')
|
246
|
-
subject.run
|
247
|
-
expect(io.string).to_not eql('')
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
context 'when the light is red' do
|
252
|
-
before do
|
253
|
-
subject.threshold.times do
|
254
|
-
subject.data_store.record_failure(subject, failure)
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
it 'raises an error' do
|
259
|
-
expect { subject.run }.to raise_error(Stoplight::Error::RedLight)
|
260
|
-
end
|
261
|
-
|
262
|
-
it 'uses the name as the error message' do
|
263
|
-
e =
|
264
|
-
begin
|
265
|
-
subject.run
|
266
|
-
rescue Stoplight::Error::RedLight => e
|
267
|
-
e
|
268
|
-
end
|
269
|
-
expect(e.message).to eql(subject.name)
|
270
|
-
end
|
271
|
-
|
272
|
-
context 'with a fallback' do
|
273
|
-
before { subject.with_fallback(&fallback) }
|
274
|
-
|
275
|
-
it 'runs the fallback' do
|
276
|
-
expect(subject.run).to eql(fallback_result)
|
277
|
-
end
|
278
|
-
|
279
|
-
it 'does not pass anything to the fallback' do
|
280
|
-
subject.with_fallback do |e|
|
281
|
-
expect(e).to eql(nil)
|
282
|
-
fallback_result
|
283
|
-
end
|
284
|
-
expect(subject.run).to eql(fallback_result)
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
26
|
+
it_behaves_like 'Stoplight::Light::Runnable#color'
|
27
|
+
it_behaves_like 'Stoplight::Light::Runnable#run'
|
28
|
+
end
|
288
29
|
|
289
|
-
|
290
|
-
|
30
|
+
context 'with redis data store', :redis do
|
31
|
+
let(:data_store) { Stoplight::DataStore::Redis.new(redis) }
|
291
32
|
|
292
|
-
|
293
|
-
|
294
|
-
end
|
295
|
-
end
|
33
|
+
it_behaves_like 'Stoplight::Light::Runnable#color'
|
34
|
+
it_behaves_like 'Stoplight::Light::Runnable#run'
|
296
35
|
end
|
297
36
|
end
|
@@ -122,19 +122,9 @@ RSpec.describe Stoplight::Light do
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
125
|
-
describe '#
|
126
|
-
it '
|
127
|
-
|
128
|
-
light.with_cool_off_time(cool_off_time)
|
129
|
-
expect(light.cool_off_time).to eql(cool_off_time)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
describe '#with_data_store' do
|
134
|
-
it 'sets the data store' do
|
135
|
-
data_store = Stoplight::DataStore::Memory.new
|
136
|
-
light.with_data_store(data_store)
|
137
|
-
expect(light.data_store).to eql(data_store)
|
125
|
+
describe '#window_size' do
|
126
|
+
it 'is initially the default' do
|
127
|
+
expect(light.window_size).to eql(Stoplight::Default::WINDOW_SIZE)
|
138
128
|
end
|
139
129
|
end
|
140
130
|
|
@@ -162,19 +152,5 @@ RSpec.describe Stoplight::Light do
|
|
162
152
|
end
|
163
153
|
end
|
164
154
|
|
165
|
-
|
166
|
-
it 'sets the notifiers' do
|
167
|
-
notifiers = [Stoplight::Notifier::IO.new(StringIO.new)]
|
168
|
-
light.with_notifiers(notifiers)
|
169
|
-
expect(light.notifiers).to eql(notifiers)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
describe '#with_threshold' do
|
174
|
-
it 'sets the threshold' do
|
175
|
-
threshold = 12
|
176
|
-
light.with_threshold(threshold)
|
177
|
-
expect(light.threshold).to eql(threshold)
|
178
|
-
end
|
179
|
-
end
|
155
|
+
it_behaves_like Stoplight::Configurable
|
180
156
|
end
|
@@ -2,49 +2,49 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
RSpec.
|
6
|
-
|
7
|
-
|
5
|
+
RSpec.describe Stoplight::Notifier::Generic do
|
6
|
+
let(:light) { Stoplight::Light.new(name) {} }
|
7
|
+
let(:name) { ('a'..'z').to_a.shuffle.join }
|
8
|
+
let(:from_color) { Stoplight::Color::GREEN }
|
9
|
+
let(:to_color) { Stoplight::Color::RED }
|
10
|
+
let(:error) { nil }
|
11
|
+
|
12
|
+
it 'is a module' do
|
13
|
+
expect(described_class).to be_a(Module)
|
8
14
|
end
|
9
15
|
|
10
|
-
describe '#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
describe '#put' do
|
17
|
+
let(:notifier) { notifier_class.new(double.as_null_object) }
|
18
|
+
let(:notifier_class) do
|
19
|
+
Class.new do
|
20
|
+
include Stoplight::Notifier::Generic
|
21
|
+
end
|
15
22
|
end
|
16
23
|
|
17
|
-
it '
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
it 'has to implement the #put method' do
|
25
|
+
expect do
|
26
|
+
notifier.notify(light, from_color, to_color, error)
|
27
|
+
end.to raise_error(NotImplementedError)
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
24
31
|
describe '#notify' do
|
25
|
-
let(:
|
26
|
-
let(:
|
27
|
-
let(:
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
.to eql(notifier.formatter.call(light, from_color, to_color, error))
|
32
|
+
let(:formatted_message) { 'formatted message' }
|
33
|
+
let(:notifier) { notifier_class.new(object) }
|
34
|
+
let(:notifier_class) do
|
35
|
+
Class.new do
|
36
|
+
include Stoplight::Notifier::Generic
|
37
|
+
def put(message)
|
38
|
+
object.put(message)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'puts formatted message' do
|
43
|
+
expect(formatter).to receive(:call).with(light, from_color, to_color, error) { formatted_message }
|
44
|
+
expect(object).to receive(:put).with(formatted_message)
|
45
|
+
|
46
|
+
expect(notifier.notify(light, from_color, to_color, error)).to eq(formatted_message)
|
47
|
+
end
|
42
48
|
end
|
43
49
|
end
|
44
50
|
end
|
45
|
-
|
46
|
-
RSpec.describe Stoplight::Notifier::Generic do
|
47
|
-
it 'is a module' do
|
48
|
-
expect(described_class).to be_a(Module)
|
49
|
-
end
|
50
|
-
end
|
data/spec/stoplight_spec.rb
CHANGED
@@ -9,13 +9,24 @@ RSpec.describe Stoplight do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
RSpec.describe 'Stoplight' do
|
12
|
-
subject(:light) { Stoplight(name, &code) }
|
13
12
|
let(:name) { ('a'..'z').to_a.shuffle.join }
|
14
|
-
let(:code) { -> {} }
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
context 'with code' do
|
15
|
+
subject(:light) { Stoplight(name, &code) }
|
16
|
+
let(:code) { -> {} }
|
17
|
+
|
18
|
+
it 'creates a stoplight' do
|
19
|
+
expect(light).to be_a(Stoplight::Light)
|
20
|
+
expect(light.name).to eql(name)
|
21
|
+
expect(light.code).to eql(code)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'without code' do
|
26
|
+
subject(:light) { Stoplight(name) }
|
27
|
+
|
28
|
+
it 'creates a stoplight' do
|
29
|
+
expect(light).to eq(Stoplight::Builder.with(name: name))
|
30
|
+
end
|
20
31
|
end
|
21
32
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples Stoplight::Configurable do
|
4
|
+
let(:configurable) { described_class.new(configuration) }
|
5
|
+
|
6
|
+
let(:configuration) do
|
7
|
+
Stoplight::Configuration.new(
|
8
|
+
name: name,
|
9
|
+
data_store: Stoplight.default_data_store,
|
10
|
+
notifiers: Stoplight.default_notifiers,
|
11
|
+
error_notifier: Stoplight.default_error_notifier,
|
12
|
+
cool_off_time: Stoplight::Default::COOL_OFF_TIME,
|
13
|
+
threshold: Stoplight::Default::THRESHOLD,
|
14
|
+
window_size: Stoplight::Default::WINDOW_SIZE
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
shared_examples 'configurable attribute' do |attribute|
|
19
|
+
subject(:with_attribute) do
|
20
|
+
configurable.__send__("with_#{attribute}", __send__(attribute))
|
21
|
+
end
|
22
|
+
|
23
|
+
it "configures #{attribute}" do
|
24
|
+
expect(with_attribute.configuration.__send__(attribute)).to eq(__send__(attribute))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#with_data_store' do
|
29
|
+
let(:data_store) { instance_double(Stoplight::DataStore::Redis) }
|
30
|
+
|
31
|
+
include_examples 'configurable attribute', :data_store
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#cool_off_time' do
|
35
|
+
let(:cool_off_time) { 1_000 }
|
36
|
+
|
37
|
+
include_examples 'configurable attribute', :cool_off_time
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#with_threshold' do
|
41
|
+
let(:threshold) { 1_000 }
|
42
|
+
|
43
|
+
include_examples 'configurable attribute', :threshold
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#with_window_size' do
|
47
|
+
let(:window_size) { 1_000 }
|
48
|
+
|
49
|
+
include_examples 'configurable attribute', :window_size
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#with_notifiers' do
|
53
|
+
let(:notifiers) { 1_000 }
|
54
|
+
|
55
|
+
include_examples 'configurable attribute', :notifiers
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#with_error_notifier' do
|
59
|
+
let(:error_notifier) { ->(x) { x } }
|
60
|
+
|
61
|
+
subject(:with_attribute) do
|
62
|
+
configurable.with_error_notifier(&error_notifier)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'configures error notifier' do
|
66
|
+
expect(with_attribute.configuration.error_notifier).to eq(error_notifier)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#clear_failures' do
|
4
|
+
before do
|
5
|
+
data_store.record_failure(light, failure)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'returns the failures' do
|
9
|
+
expect(data_store.clear_failures(light)).to contain_exactly(failure)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'clears the failures' do
|
13
|
+
expect do
|
14
|
+
data_store.clear_failures(light)
|
15
|
+
end.to change { data_store.get_failures(light) }
|
16
|
+
.from([failure]).to(be_empty)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#clear_state' do
|
4
|
+
let(:state) { 'state' }
|
5
|
+
|
6
|
+
it 'returns the state' do
|
7
|
+
data_store.set_state(light, state)
|
8
|
+
|
9
|
+
expect(data_store.clear_state(light)).to eql(state)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'clears the state' do
|
13
|
+
data_store.set_state(light, state)
|
14
|
+
|
15
|
+
expect do
|
16
|
+
data_store.clear_state(light)
|
17
|
+
end.to change { data_store.get_state(light) }
|
18
|
+
.from(state).to(Stoplight::State::UNLOCKED)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#get_all' do
|
4
|
+
context 'when there are no errors' do
|
5
|
+
it 'returns the failures and the state' do
|
6
|
+
failures, state = data_store.get_all(light)
|
7
|
+
|
8
|
+
expect(failures).to eql([])
|
9
|
+
expect(state).to eql(Stoplight::State::UNLOCKED)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when there are errors' do
|
14
|
+
before do
|
15
|
+
data_store.record_failure(light, failure)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns the failures and the state' do
|
19
|
+
failures, state = data_store.get_all(light)
|
20
|
+
|
21
|
+
expect(failures).to eq([failure])
|
22
|
+
expect(state).to eql(Stoplight::State::UNLOCKED)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when there is a failure outside of the window' do
|
27
|
+
let(:window_size) { 3600 }
|
28
|
+
let(:older_failure) { Stoplight::Failure.new('class', 'message 3', Time.new - window_size - 1) }
|
29
|
+
|
30
|
+
before do
|
31
|
+
light.with_window_size(window_size)
|
32
|
+
|
33
|
+
data_store.record_failure(light, older_failure)
|
34
|
+
data_store.record_failure(light, failure)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns the failures within window and the state' do
|
38
|
+
failures, state = data_store.get_all(light)
|
39
|
+
|
40
|
+
expect(failures).to contain_exactly(failure)
|
41
|
+
expect(state).to eql(Stoplight::State::UNLOCKED)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#get_failures' do
|
4
|
+
it 'is initially empty' do
|
5
|
+
expect(data_store.get_failures(light)).to eql([])
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'handles invalid JSON' do
|
9
|
+
expect { data_store.record_failure(light, failure) }
|
10
|
+
.to change { data_store.get_failures(light) }
|
11
|
+
.from(be_empty)
|
12
|
+
.to(contain_exactly(failure))
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when there is a failure outside of the window' do
|
16
|
+
let(:window_size) { 3600 }
|
17
|
+
let(:older_failure) { Stoplight::Failure.new('class', 'message 3', Time.new - window_size - 1) }
|
18
|
+
|
19
|
+
before do
|
20
|
+
light.with_window_size(window_size)
|
21
|
+
|
22
|
+
data_store.record_failure(light, failure)
|
23
|
+
data_store.record_failure(light, older_failure)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns failures withing given window' do
|
27
|
+
expect(data_store.get_failures(light)).to contain_exactly(failure)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|