stoplight 3.0.0 → 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 -198
- 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/base.rb +9 -0
- data/lib/stoplight/data_store/memory.rb +46 -5
- data/lib/stoplight/data_store/redis.rb +75 -6
- 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 +34 -16
- 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 +33 -10
- 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/base_spec.rb +7 -0
- data/spec/stoplight/data_store/memory_spec.rb +12 -123
- data/spec/stoplight/data_store/redis_spec.rb +28 -129
- 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 -233
- 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 +56 -225
- data/lib/stoplight/notifier/bugsnag.rb +0 -37
- data/lib/stoplight/notifier/hip_chat.rb +0 -43
- 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/hip_chat_spec.rb +0 -91
- 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
@@ -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
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#names' do
|
4
|
+
it 'is initially empty' do
|
5
|
+
expect(data_store.names).to eql([])
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'contains the name of a light with a failure' do
|
9
|
+
data_store.record_failure(light, failure)
|
10
|
+
expect(data_store.names).to eql([light.name])
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'contains the name of a light with a set state' do
|
14
|
+
data_store.set_state(light, Stoplight::State::UNLOCKED)
|
15
|
+
expect(data_store.names).to eql([light.name])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'does not duplicate names' do
|
19
|
+
data_store.record_failure(light, failure)
|
20
|
+
data_store.set_state(light, Stoplight::State::UNLOCKED)
|
21
|
+
expect(data_store.names).to eql([light.name])
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'supports names containing colons' do
|
25
|
+
light = Stoplight::Light.new('http://api.example.com/some/action')
|
26
|
+
data_store.record_failure(light, failure)
|
27
|
+
expect(data_store.names).to eql([light.name])
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#record_failure' do
|
4
|
+
it 'returns the number of failures' do
|
5
|
+
expect(data_store.record_failure(light, failure)).to eql(1)
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'when there is an error' do
|
9
|
+
before do
|
10
|
+
data_store.record_failure(light, failure)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'persists the failure' do
|
14
|
+
expect(data_store.get_failures(light)).to eq([failure])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when there is are several errors' do
|
19
|
+
before do
|
20
|
+
data_store.record_failure(light, failure)
|
21
|
+
data_store.record_failure(light, other)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'stores more recent failures at the head' do
|
25
|
+
expect(data_store.get_failures(light)).to eq([other, failure])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
shared_examples 'with_threshold' do
|
30
|
+
context 'when the number of errors is bigger then threshold' do
|
31
|
+
before do
|
32
|
+
light.with_threshold(1)
|
33
|
+
|
34
|
+
data_store.record_failure(light, failure)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'limits the number of stored failures' do
|
38
|
+
expect do
|
39
|
+
data_store.record_failure(light, other)
|
40
|
+
end.to change { data_store.get_failures(light) }
|
41
|
+
.from([failure])
|
42
|
+
.to([other])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it_behaves_like 'with_threshold'
|
48
|
+
|
49
|
+
context 'with window_size' do
|
50
|
+
let(:window_size) { 3600 }
|
51
|
+
|
52
|
+
before do
|
53
|
+
light.with_window_size(window_size)
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when error is outside of the window' do
|
57
|
+
let(:older_failure) { Stoplight::Failure.new('class', 'message 3', Time.new - window_size - 1) }
|
58
|
+
|
59
|
+
it 'stores failures only withing window length' do
|
60
|
+
data_store.record_failure(light, failure)
|
61
|
+
data_store.record_failure(light, other)
|
62
|
+
data_store.record_failure(light, older_failure)
|
63
|
+
|
64
|
+
expect(data_store.get_failures(light)).to eq([other, failure])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it_behaves_like 'with_threshold'
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#set_state' do
|
4
|
+
let(:state) { 'state' }
|
5
|
+
|
6
|
+
it 'returns the state' do
|
7
|
+
expect(data_store.set_state(light, state)).to eql(state)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'persists the state' do
|
11
|
+
data_store.set_state(light, state)
|
12
|
+
|
13
|
+
expect(data_store.get_state(light)).to eql(state)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base#with_notification_lock' do
|
4
|
+
context 'when notification is already sent' do
|
5
|
+
before do
|
6
|
+
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'does not yield passed block' do
|
10
|
+
expect do |b|
|
11
|
+
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED, &b)
|
12
|
+
end.not_to yield_control
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when notification is not already sent' do
|
17
|
+
before do
|
18
|
+
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'yields passed block' do
|
22
|
+
expect do |b|
|
23
|
+
data_store.with_notification_lock(light, Stoplight::Color::RED, Stoplight::Color::GREEN, &b)
|
24
|
+
end.to yield_control
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base/names'
|
4
|
+
require_relative 'base/get_failures'
|
5
|
+
require_relative 'base/get_all'
|
6
|
+
require_relative 'base/record_failures'
|
7
|
+
require_relative 'base/clear_failures'
|
8
|
+
require_relative 'base/get_state'
|
9
|
+
require_relative 'base/set_state'
|
10
|
+
require_relative 'base/clear_state'
|
11
|
+
require_relative 'base/with_notification_lock'
|
12
|
+
|
13
|
+
RSpec.shared_examples 'Stoplight::DataStore::Base' do
|
14
|
+
it 'is a class' do
|
15
|
+
expect(described_class).to be_a(Class)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'is a subclass of Base' do
|
19
|
+
expect(described_class).to be < Stoplight::DataStore::Base
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis'
|
4
|
+
require 'database_cleaner/redis'
|
5
|
+
|
6
|
+
cleaning_strategy = DatabaseCleaner::Redis::Deletion.new(only: ["#{Stoplight::DataStore::Redis::KEY_PREFIX}*"])
|
7
|
+
DatabaseCleaner.strategy = cleaning_strategy
|
8
|
+
|
9
|
+
RSpec.shared_context :redis, :redis do
|
10
|
+
let(:redis) { Redis.new(url: ENV.fetch('STOPLIGHT_REDIS_URL', 'redis://127.0.0.1:6379/0')) }
|
11
|
+
|
12
|
+
before(:suite) do
|
13
|
+
DatabaseCleaner[:redis].db = redis
|
14
|
+
DatabaseCleaner.clean_with(:deletion)
|
15
|
+
end
|
16
|
+
|
17
|
+
around(:each) do |example|
|
18
|
+
DatabaseCleaner.cleaning do
|
19
|
+
example.run
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec.configure do |config|
|
25
|
+
config.include_context :redis, include_shared: true
|
26
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'Stoplight::Light::Runnable#color' do
|
4
|
+
subject(:light) { Stoplight::Light.new(name) }
|
5
|
+
|
6
|
+
let(:name) { random_string }
|
7
|
+
|
8
|
+
it 'is initially green' do
|
9
|
+
expect(light.color).to eql(Stoplight::Color::GREEN)
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when its locked green' do
|
13
|
+
before do
|
14
|
+
light.data_store.set_state(light, Stoplight::State::LOCKED_GREEN)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'is green' do
|
18
|
+
expect(light.color).to eql(Stoplight::Color::GREEN)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when its locked red' do
|
23
|
+
before do
|
24
|
+
light.data_store.set_state(light, Stoplight::State::LOCKED_RED)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'is red' do
|
28
|
+
expect(light.color).to eql(Stoplight::Color::RED)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when there are many failures' do
|
33
|
+
let(:anther) { Stoplight::Failure.new(error.class.name, error.message, time - 10) }
|
34
|
+
|
35
|
+
before do
|
36
|
+
light.with_threshold(2)
|
37
|
+
light.data_store.record_failure(light, failure)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'turns red' do
|
41
|
+
expect do
|
42
|
+
light.data_store.record_failure(light, anther)
|
43
|
+
end.to change(light, :color).to be(Stoplight::Color::RED)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when the most recent failure is old' do
|
48
|
+
let(:failure) { Stoplight::Failure.new(error.class.name, error.message, Time.new - light.cool_off_time) }
|
49
|
+
let(:failure2) { Stoplight::Failure.new(error.class.name, error.message, Time.new - light.cool_off_time - 10) }
|
50
|
+
|
51
|
+
before do
|
52
|
+
light.with_threshold(2)
|
53
|
+
light.data_store.record_failure(light, failure2)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'turns yellow' do
|
57
|
+
expect do
|
58
|
+
light.data_store.record_failure(light, failure)
|
59
|
+
end.to change(light, :color).to be(Stoplight::Color::YELLOW)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when the least recent failure is old' do
|
64
|
+
let(:other) do
|
65
|
+
Stoplight::Failure.new(error.class.name, error.message, Time.new - light.cool_off_time)
|
66
|
+
end
|
67
|
+
|
68
|
+
before do
|
69
|
+
light.with_threshold(2)
|
70
|
+
light.data_store.record_failure(light, other)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'is red when the least recent failure is old' do
|
74
|
+
expect do
|
75
|
+
light.data_store.record_failure(light, failure)
|
76
|
+
end.to change(light, :color).to be(Stoplight::Color::RED)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|