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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +176 -180
  3. data/lib/stoplight/builder.rb +68 -0
  4. data/lib/stoplight/circuit_breaker.rb +92 -0
  5. data/lib/stoplight/configurable.rb +95 -0
  6. data/lib/stoplight/configuration.rb +126 -0
  7. data/lib/stoplight/data_store/memory.rb +20 -5
  8. data/lib/stoplight/data_store/redis.rb +37 -5
  9. data/lib/stoplight/default.rb +2 -0
  10. data/lib/stoplight/error.rb +1 -0
  11. data/lib/stoplight/light/deprecated.rb +44 -0
  12. data/lib/stoplight/light/lockable.rb +45 -0
  13. data/lib/stoplight/light/runnable.rb +25 -24
  14. data/lib/stoplight/light.rb +69 -63
  15. data/lib/stoplight/rspec/generic_notifier.rb +42 -0
  16. data/lib/stoplight/rspec.rb +3 -0
  17. data/lib/stoplight/version.rb +1 -1
  18. data/lib/stoplight.rb +32 -8
  19. data/spec/spec_helper.rb +7 -0
  20. data/spec/stoplight/builder_spec.rb +165 -0
  21. data/spec/stoplight/circuit_breaker_spec.rb +35 -0
  22. data/spec/stoplight/configurable_spec.rb +25 -0
  23. data/spec/stoplight/data_store/memory_spec.rb +12 -149
  24. data/spec/stoplight/data_store/redis_spec.rb +26 -158
  25. data/spec/stoplight/error_spec.rb +10 -0
  26. data/spec/stoplight/light/lockable_spec.rb +93 -0
  27. data/spec/stoplight/light/runnable_spec.rb +12 -273
  28. data/spec/stoplight/light_spec.rb +4 -28
  29. data/spec/stoplight/notifier/generic_spec.rb +35 -35
  30. data/spec/stoplight/notifier/io_spec.rb +1 -0
  31. data/spec/stoplight/notifier/logger_spec.rb +3 -0
  32. data/spec/stoplight_spec.rb +17 -6
  33. data/spec/support/configurable.rb +69 -0
  34. data/spec/support/data_store/base/clear_failures.rb +18 -0
  35. data/spec/support/data_store/base/clear_state.rb +20 -0
  36. data/spec/support/data_store/base/get_all.rb +44 -0
  37. data/spec/support/data_store/base/get_failures.rb +30 -0
  38. data/spec/support/data_store/base/get_state.rb +7 -0
  39. data/spec/support/data_store/base/names.rb +29 -0
  40. data/spec/support/data_store/base/record_failures.rb +70 -0
  41. data/spec/support/data_store/base/set_state.rb +15 -0
  42. data/spec/support/data_store/base/with_notification_lock.rb +27 -0
  43. data/spec/support/data_store/base.rb +21 -0
  44. data/spec/support/database_cleaner.rb +26 -0
  45. data/spec/support/exception_helpers.rb +9 -0
  46. data/spec/support/light/runnable/color.rb +79 -0
  47. data/spec/support/light/runnable/run.rb +247 -0
  48. data/spec/support/light/runnable.rb +4 -0
  49. metadata +51 -231
  50. data/lib/stoplight/notifier/bugsnag.rb +0 -37
  51. data/lib/stoplight/notifier/honeybadger.rb +0 -44
  52. data/lib/stoplight/notifier/pagerduty.rb +0 -21
  53. data/lib/stoplight/notifier/raven.rb +0 -40
  54. data/lib/stoplight/notifier/rollbar.rb +0 -39
  55. data/lib/stoplight/notifier/slack.rb +0 -21
  56. data/spec/stoplight/notifier/bugsnag_spec.rb +0 -90
  57. data/spec/stoplight/notifier/honeybadger_spec.rb +0 -88
  58. data/spec/stoplight/notifier/pagerduty_spec.rb +0 -40
  59. data/spec/stoplight/notifier/raven_spec.rb +0 -90
  60. data/spec/stoplight/notifier/rollbar_spec.rb +0 -90
  61. 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
- describe '#color' do
28
- it 'is initially green' do
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
- describe '#run' do
73
- let(:notifiers) { [notifier] }
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
- it 'records the failure when the handler does nothing' do
165
- subject.with_error_handler { |_error, _handler| }
166
- expect { result }
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
- context 'when the code block is missing' do
290
- subject { Stoplight::Light.new(name) }
30
+ context 'with redis data store', :redis do
31
+ let(:data_store) { Stoplight::DataStore::Redis.new(redis) }
291
32
 
292
- it 'raises an ArgumentError error' do
293
- expect { subject.run }.to raise_error(ArgumentError)
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 '#with_cool_off_time' do
126
- it 'sets the cool off time' do
127
- cool_off_time = 1.2
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
- describe '#with_notifiers' do
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.shared_examples_for 'a generic notifier' do
6
- it 'includes Generic' do
7
- expect(described_class).to include(Stoplight::Notifier::Generic)
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 '#formatter' do
11
- it 'is initially the default' do
12
- formatter = nil
13
- expect(described_class.new(nil, formatter).formatter)
14
- .to eql(Stoplight::Default::FORMATTER)
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 'reads the formatter' do
18
- formatter = proc {}
19
- expect(described_class.new(nil, formatter).formatter)
20
- .to eql(formatter)
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(:light) { Stoplight::Light.new(name, &code) }
26
- let(:name) { ('a'..'z').to_a.shuffle.join }
27
- let(:code) { -> {} }
28
- let(:from_color) { Stoplight::Color::GREEN }
29
- let(:to_color) { Stoplight::Color::RED }
30
- let(:notifier) { described_class.new(double.as_null_object) }
31
-
32
- it 'returns the message' do
33
- error = nil
34
- expect(notifier.notify(light, from_color, to_color, error))
35
- .to eql(notifier.formatter.call(light, from_color, to_color, error))
36
- end
37
-
38
- it 'returns the message with an error' do
39
- error = ZeroDivisionError.new('divided by 0')
40
- expect(notifier.notify(light, from_color, to_color, error))
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'spec_helper'
4
4
  require 'stringio'
5
+ require 'stoplight/rspec'
5
6
 
6
7
  RSpec.describe Stoplight::Notifier::IO do
7
8
  it_behaves_like 'a generic notifier'
@@ -3,8 +3,11 @@
3
3
  require 'spec_helper'
4
4
  require 'logger'
5
5
  require 'stringio'
6
+ require 'stoplight/rspec'
6
7
 
7
8
  RSpec.describe Stoplight::Notifier::Logger do
9
+ it_behaves_like 'a generic notifier'
10
+
8
11
  it 'is a class' do
9
12
  expect(described_class).to be_a(Class)
10
13
  end
@@ -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
- it 'creates a stoplight' do
17
- expect(light).to be_a(Stoplight::Light)
18
- expect(light.name).to eql(name)
19
- expect(light.code).to eql(code)
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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples 'Stoplight::DataStore::Base#get_state' do
4
+ it 'is initially unlocked' do
5
+ expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
6
+ end
7
+ end