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
@@ -7,127 +7,16 @@ RSpec.describe Stoplight::DataStore::Memory do
|
|
7
7
|
let(:light) { Stoplight::Light.new(name) {} }
|
8
8
|
let(:name) { ('a'..'z').to_a.shuffle.join }
|
9
9
|
let(:failure) { Stoplight::Failure.new('class', 'message', Time.new) }
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'contains the name of a light with a failure' do
|
25
|
-
data_store.record_failure(light, failure)
|
26
|
-
expect(data_store.names).to eql([light.name])
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'contains the name of a light with a set state' do
|
30
|
-
data_store.set_state(light, Stoplight::State::UNLOCKED)
|
31
|
-
expect(data_store.names).to eql([light.name])
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'does not duplicate names' do
|
35
|
-
data_store.record_failure(light, failure)
|
36
|
-
data_store.set_state(light, Stoplight::State::UNLOCKED)
|
37
|
-
expect(data_store.names).to eql([light.name])
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'supports names containing colons' do
|
41
|
-
light = Stoplight::Light.new('http://api.example.com/some/action')
|
42
|
-
data_store.record_failure(light, failure)
|
43
|
-
expect(data_store.names).to eql([light.name])
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
describe '#get_all' do
|
48
|
-
it 'returns the failures and the state' do
|
49
|
-
failures, state = data_store.get_all(light)
|
50
|
-
expect(failures).to eql([])
|
51
|
-
expect(state).to eql(Stoplight::State::UNLOCKED)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '#get_failures' do
|
56
|
-
it 'is initially empty' do
|
57
|
-
expect(data_store.get_failures(light)).to eql([])
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
describe '#record_failure' do
|
62
|
-
it 'returns the number of failures' do
|
63
|
-
expect(data_store.record_failure(light, failure)).to eql(1)
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'persists the failure' do
|
67
|
-
data_store.record_failure(light, failure)
|
68
|
-
expect(data_store.get_failures(light)).to eql([failure])
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'stores more recent failures at the front' do
|
72
|
-
data_store.record_failure(light, failure)
|
73
|
-
other = Stoplight::Failure.new('class', 'message 2', Time.new)
|
74
|
-
data_store.record_failure(light, other)
|
75
|
-
expect(data_store.get_failures(light)).to eql([other, failure])
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'limits the number of stored failures' do
|
79
|
-
light.with_threshold(1)
|
80
|
-
data_store.record_failure(light, failure)
|
81
|
-
other = Stoplight::Failure.new('class', 'message 2', Time.new)
|
82
|
-
data_store.record_failure(light, other)
|
83
|
-
expect(data_store.get_failures(light)).to eql([other])
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe '#clear_failures' do
|
88
|
-
it 'returns the failures' do
|
89
|
-
data_store.record_failure(light, failure)
|
90
|
-
expect(data_store.clear_failures(light)).to eql([failure])
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'clears the failures' do
|
94
|
-
data_store.record_failure(light, failure)
|
95
|
-
data_store.clear_failures(light)
|
96
|
-
expect(data_store.get_failures(light)).to eql([])
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
describe '#get_state' do
|
101
|
-
it 'is initially unlocked' do
|
102
|
-
expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
describe '#set_state' do
|
107
|
-
it 'returns the state' do
|
108
|
-
state = 'state'
|
109
|
-
expect(data_store.set_state(light, state)).to eql(state)
|
110
|
-
end
|
111
|
-
|
112
|
-
it 'persists the state' do
|
113
|
-
state = 'state'
|
114
|
-
data_store.set_state(light, state)
|
115
|
-
expect(data_store.get_state(light)).to eql(state)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe '#clear_state' do
|
120
|
-
it 'returns the state' do
|
121
|
-
state = 'state'
|
122
|
-
data_store.set_state(light, state)
|
123
|
-
expect(data_store.clear_state(light)).to eql(state)
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'clears the state' do
|
127
|
-
state = 'state'
|
128
|
-
data_store.set_state(light, state)
|
129
|
-
data_store.clear_state(light)
|
130
|
-
expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
|
131
|
-
end
|
132
|
-
end
|
10
|
+
let(:other) { Stoplight::Failure.new('class', 'message 2', Time.new) }
|
11
|
+
|
12
|
+
it_behaves_like 'Stoplight::DataStore::Base'
|
13
|
+
it_behaves_like 'Stoplight::DataStore::Base#names'
|
14
|
+
it_behaves_like 'Stoplight::DataStore::Base#get_failures'
|
15
|
+
it_behaves_like 'Stoplight::DataStore::Base#get_all'
|
16
|
+
it_behaves_like 'Stoplight::DataStore::Base#record_failure'
|
17
|
+
it_behaves_like 'Stoplight::DataStore::Base#clear_failures'
|
18
|
+
it_behaves_like 'Stoplight::DataStore::Base#get_state'
|
19
|
+
it_behaves_like 'Stoplight::DataStore::Base#set_state'
|
20
|
+
it_behaves_like 'Stoplight::DataStore::Base#clear_state'
|
21
|
+
it_behaves_like 'Stoplight::DataStore::Base#with_notification_lock'
|
133
22
|
end
|
@@ -1,146 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
|
-
require 'fakeredis'
|
5
4
|
|
6
|
-
RSpec.describe Stoplight::DataStore::Redis do
|
7
|
-
let(:data_store) { described_class.new(redis) }
|
8
|
-
let(:
|
5
|
+
RSpec.describe Stoplight::DataStore::Redis, :redis do
|
6
|
+
let(:data_store) { described_class.new(redis, redlock: redlock) }
|
7
|
+
let(:redlock) { instance_double(Redlock::Client) }
|
9
8
|
let(:light) { Stoplight::Light.new(name) {} }
|
10
9
|
let(:name) { ('a'..'z').to_a.shuffle.join }
|
11
|
-
let(:failure) { Stoplight::Failure.new('class', 'message', Time.new) }
|
10
|
+
let(:failure) { Stoplight::Failure.new('class', 'message', Time.new - 60) }
|
11
|
+
let(:other) { Stoplight::Failure.new('class', 'message 2', Time.new) }
|
12
12
|
|
13
|
-
|
13
|
+
it_behaves_like 'Stoplight::DataStore::Base'
|
14
|
+
it_behaves_like 'Stoplight::DataStore::Base#names'
|
15
|
+
it_behaves_like 'Stoplight::DataStore::Base#get_all'
|
16
|
+
it_behaves_like 'Stoplight::DataStore::Base#record_failure'
|
17
|
+
it_behaves_like 'Stoplight::DataStore::Base#clear_failures'
|
18
|
+
it_behaves_like 'Stoplight::DataStore::Base#get_state'
|
19
|
+
it_behaves_like 'Stoplight::DataStore::Base#set_state'
|
20
|
+
it_behaves_like 'Stoplight::DataStore::Base#clear_state'
|
14
21
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
expect(described_class).to be < Stoplight::DataStore::Base
|
21
|
-
end
|
22
|
+
it_behaves_like 'Stoplight::DataStore::Base#get_failures' do
|
23
|
+
context 'when JSON is invalid' do
|
24
|
+
before do
|
25
|
+
light.with_error_notifier { |_error| }
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
expect(data_store.names).to eql([])
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'contains the name of a light with a failure' do
|
29
|
-
data_store.record_failure(light, failure)
|
30
|
-
expect(data_store.names).to eql([light.name])
|
31
|
-
end
|
28
|
+
it 'handles it without an error' do
|
29
|
+
expect(failure).to receive(:to_json).and_return('invalid JSON')
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
it 'does not duplicate names' do
|
39
|
-
data_store.record_failure(light, failure)
|
40
|
-
data_store.set_state(light, Stoplight::State::UNLOCKED)
|
41
|
-
expect(data_store.names).to eql([light.name])
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'supports names containing colons' do
|
45
|
-
light = Stoplight::Light.new('http://api.example.com/some/action')
|
46
|
-
data_store.record_failure(light, failure)
|
47
|
-
expect(data_store.names).to eql([light.name])
|
31
|
+
expect { data_store.record_failure(light, failure) }
|
32
|
+
.to change { data_store.get_failures(light) }
|
33
|
+
.to([have_attributes(error_class: 'JSON::ParserError')])
|
34
|
+
end
|
48
35
|
end
|
49
36
|
end
|
50
37
|
|
51
|
-
|
52
|
-
|
53
|
-
failures, state = data_store.get_all(light)
|
54
|
-
expect(failures).to eql([])
|
55
|
-
expect(state).to eql(Stoplight::State::UNLOCKED)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
describe '#get_failures' do
|
60
|
-
it 'is initially empty' do
|
61
|
-
expect(data_store.get_failures(light)).to eql([])
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'handles invalid JSON' do
|
65
|
-
expect(redis.keys.size).to eql(0)
|
66
|
-
data_store.record_failure(light, failure)
|
67
|
-
expect(redis.keys.size).to eql(1)
|
68
|
-
redis.lset(redis.keys.first, 0, 'invalid JSON')
|
69
|
-
light.with_error_notifier { |_error| }
|
70
|
-
expect(data_store.get_failures(light).size).to eql(1)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
describe '#record_failure' do
|
75
|
-
it 'returns the number of failures' do
|
76
|
-
expect(data_store.record_failure(light, failure)).to eql(1)
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'persists the failure' do
|
80
|
-
data_store.record_failure(light, failure)
|
81
|
-
expect(data_store.get_failures(light)).to eq([failure])
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'stores more recent failures at the head' do
|
85
|
-
data_store.record_failure(light, failure)
|
86
|
-
other = Stoplight::Failure.new('class', 'message 2', Time.new)
|
87
|
-
data_store.record_failure(light, other)
|
88
|
-
expect(data_store.get_failures(light)).to eq([other, failure])
|
89
|
-
end
|
90
|
-
|
91
|
-
it 'limits the number of stored failures' do
|
92
|
-
light.with_threshold(1)
|
93
|
-
data_store.record_failure(light, failure)
|
94
|
-
other = Stoplight::Failure.new('class', 'message 2', Time.new)
|
95
|
-
data_store.record_failure(light, other)
|
96
|
-
expect(data_store.get_failures(light)).to eq([other])
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
describe '#clear_failures' do
|
101
|
-
it 'returns the failures' do
|
102
|
-
data_store.record_failure(light, failure)
|
103
|
-
expect(data_store.clear_failures(light)).to eq([failure])
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'clears the failures' do
|
107
|
-
data_store.record_failure(light, failure)
|
108
|
-
data_store.clear_failures(light)
|
109
|
-
expect(data_store.get_failures(light)).to eql([])
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
describe '#get_state' do
|
114
|
-
it 'is initially unlocked' do
|
115
|
-
expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe '#set_state' do
|
120
|
-
it 'returns the state' do
|
121
|
-
state = 'state'
|
122
|
-
expect(data_store.set_state(light, state)).to eql(state)
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'persists the state' do
|
126
|
-
state = 'state'
|
127
|
-
data_store.set_state(light, state)
|
128
|
-
expect(data_store.get_state(light)).to eql(state)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
describe '#clear_state' do
|
133
|
-
it 'returns the state' do
|
134
|
-
state = 'state'
|
135
|
-
data_store.set_state(light, state)
|
136
|
-
expect(data_store.clear_state(light)).to eql(state)
|
137
|
-
end
|
38
|
+
it_behaves_like 'Stoplight::DataStore::Base#with_notification_lock' do
|
39
|
+
let(:lock_key) { "stoplight:v4:notification_lock:#{name}" }
|
138
40
|
|
139
|
-
|
140
|
-
|
141
|
-
data_store.set_state(light, state)
|
142
|
-
data_store.clear_state(light)
|
143
|
-
expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
|
41
|
+
before do
|
42
|
+
allow(redlock).to receive(:lock).with(lock_key, 2_000).and_yield
|
144
43
|
end
|
145
44
|
end
|
146
45
|
end
|
@@ -17,6 +17,16 @@ RSpec.describe Stoplight::Error do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
describe '::IncorrectColor' do
|
21
|
+
it 'is a class' do
|
22
|
+
expect(Stoplight::Error::IncorrectColor).to be_a(Class)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'is a subclass of StandardError' do
|
26
|
+
expect(Stoplight::Error::IncorrectColor).to be < StandardError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
20
30
|
describe '::RedLight' do
|
21
31
|
it 'is a class' do
|
22
32
|
expect(Stoplight::Error::RedLight).to be_a(Class)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Stoplight::Light::Lockable do
|
6
|
+
subject(:light) { Stoplight::Light.new(name, &code) }
|
7
|
+
|
8
|
+
let(:code) { -> { code_result } }
|
9
|
+
let(:code_result) { random_string }
|
10
|
+
let(:name) { random_string }
|
11
|
+
|
12
|
+
def random_string
|
13
|
+
('a'..'z').to_a.sample(8).join
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#lock' do
|
17
|
+
let(:color) { Stoplight::Color::GREEN }
|
18
|
+
|
19
|
+
context 'with correct color' do
|
20
|
+
it 'returns the light' do
|
21
|
+
expect(light.lock(color)).to be_a Stoplight::Light
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with green color' do
|
25
|
+
let(:color) { Stoplight::Color::GREEN }
|
26
|
+
|
27
|
+
it 'locks green color' do
|
28
|
+
expect(light.data_store).to receive(:set_state).with(light, Stoplight::State::LOCKED_GREEN)
|
29
|
+
|
30
|
+
light.lock(color)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with red color' do
|
35
|
+
let(:color) { Stoplight::Color::RED }
|
36
|
+
|
37
|
+
it 'locks red color' do
|
38
|
+
expect(light.data_store).to receive(:set_state).with(light, Stoplight::State::LOCKED_RED)
|
39
|
+
|
40
|
+
light.lock(color)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with incorrect color' do
|
46
|
+
let(:color) { 'incorrect-color' }
|
47
|
+
|
48
|
+
it 'raises Error::IncorrectColor error' do
|
49
|
+
expect { light.lock(color) }.to raise_error(Stoplight::Error::IncorrectColor)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'does not lock color' do
|
53
|
+
expect(light.data_store).to_not receive(:set_state)
|
54
|
+
|
55
|
+
suppress(Stoplight::Error::IncorrectColor) { light.lock(color) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#unlock' do
|
61
|
+
it 'returns the light' do
|
62
|
+
expect(light.unlock).to be_a Stoplight::Light
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'with locked green light' do
|
66
|
+
before { light.lock(Stoplight::Color::GREEN) }
|
67
|
+
|
68
|
+
it 'unlocks light' do
|
69
|
+
expect(light.data_store).to receive(:set_state).with(light, Stoplight::State::UNLOCKED)
|
70
|
+
|
71
|
+
light.unlock
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'with locked red light' do
|
76
|
+
before { light.lock(Stoplight::Color::RED) }
|
77
|
+
|
78
|
+
it 'unlocks light' do
|
79
|
+
expect(light.data_store).to receive(:set_state).with(light, Stoplight::State::UNLOCKED)
|
80
|
+
|
81
|
+
light.unlock
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with unlocked light' do
|
86
|
+
it 'unlocks light' do
|
87
|
+
expect(light.data_store).to receive(:set_state).with(light, Stoplight::State::UNLOCKED)
|
88
|
+
|
89
|
+
light.unlock
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -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,234 +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
|
-
it 'notifies when transitioning to red' do
|
113
|
-
subject.threshold.times do
|
114
|
-
expect(io.string).to eql('')
|
115
|
-
begin
|
116
|
-
subject.run
|
117
|
-
rescue error.class
|
118
|
-
nil
|
119
|
-
end
|
120
|
-
end
|
121
|
-
expect(io.string).to_not eql('')
|
122
|
-
end
|
123
|
-
|
124
|
-
context 'with an error handler' do
|
125
|
-
let(:result) do
|
126
|
-
subject.run
|
127
|
-
expect(false).to be(true)
|
128
|
-
rescue error.class
|
129
|
-
expect(true).to be(true)
|
130
|
-
end
|
131
|
-
|
132
|
-
it 'records the failure when the handler does nothing' do
|
133
|
-
subject.with_error_handler { |_error, _handler| }
|
134
|
-
expect { result }
|
135
|
-
.to change { subject.data_store.get_failures(subject).size }
|
136
|
-
.by(1)
|
137
|
-
end
|
23
|
+
context 'with memory data store' do
|
24
|
+
let(:data_store) { Stoplight::DataStore::Memory.new }
|
138
25
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
.to change { subject.data_store.get_failures(subject).size }
|
143
|
-
.by(1)
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'does not record the failure when the handler raises' do
|
147
|
-
subject.with_error_handler { |error, _handle| raise error }
|
148
|
-
expect { result }
|
149
|
-
.to_not change { subject.data_store.get_failures(subject).size }
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
context 'with a fallback' do
|
154
|
-
before { subject.with_fallback(&fallback) }
|
155
|
-
|
156
|
-
it 'runs the fallback' do
|
157
|
-
expect(subject.run).to eql(fallback_result)
|
158
|
-
end
|
159
|
-
|
160
|
-
it 'passes the error to the fallback' do
|
161
|
-
subject.with_fallback do |e|
|
162
|
-
expect(e).to eql(error)
|
163
|
-
fallback_result
|
164
|
-
end
|
165
|
-
expect(subject.run).to eql(fallback_result)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
context 'when the data store is failing' do
|
171
|
-
let(:data_store) { Object.new }
|
172
|
-
let(:error_notifier) { ->(_) {} }
|
173
|
-
|
174
|
-
before do
|
175
|
-
subject
|
176
|
-
.with_data_store(data_store)
|
177
|
-
.with_error_notifier(&error_notifier)
|
178
|
-
end
|
179
|
-
|
180
|
-
it 'runs the code' do
|
181
|
-
expect(subject.run).to eql(code_result)
|
182
|
-
end
|
183
|
-
|
184
|
-
it 'notifies about the error' do
|
185
|
-
has_notified = false
|
186
|
-
subject.with_error_notifier do |e|
|
187
|
-
has_notified = true
|
188
|
-
expect(e).to be_a(NoMethodError)
|
189
|
-
end
|
190
|
-
subject.run
|
191
|
-
expect(has_notified).to eql(true)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
context 'when the light is yellow' do
|
197
|
-
before do
|
198
|
-
(subject.threshold - 1).times do
|
199
|
-
subject.data_store.record_failure(subject, failure)
|
200
|
-
end
|
201
|
-
|
202
|
-
other = Stoplight::Failure.new(
|
203
|
-
error.class.name, error.message, time - subject.cool_off_time
|
204
|
-
)
|
205
|
-
subject.data_store.record_failure(subject, other)
|
206
|
-
end
|
207
|
-
|
208
|
-
it 'runs the code' do
|
209
|
-
expect(subject.run).to eql(code_result)
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'notifies when transitioning to green' do
|
213
|
-
expect(io.string).to eql('')
|
214
|
-
subject.run
|
215
|
-
expect(io.string).to_not eql('')
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
context 'when the light is red' do
|
220
|
-
before do
|
221
|
-
subject.threshold.times do
|
222
|
-
subject.data_store.record_failure(subject, failure)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
it 'raises an error' do
|
227
|
-
expect { subject.run }.to raise_error(Stoplight::Error::RedLight)
|
228
|
-
end
|
229
|
-
|
230
|
-
it 'uses the name as the error message' do
|
231
|
-
e =
|
232
|
-
begin
|
233
|
-
subject.run
|
234
|
-
rescue Stoplight::Error::RedLight => e
|
235
|
-
e
|
236
|
-
end
|
237
|
-
expect(e.message).to eql(subject.name)
|
238
|
-
end
|
239
|
-
|
240
|
-
context 'with a fallback' do
|
241
|
-
before { subject.with_fallback(&fallback) }
|
26
|
+
it_behaves_like 'Stoplight::Light::Runnable#color'
|
27
|
+
it_behaves_like 'Stoplight::Light::Runnable#run'
|
28
|
+
end
|
242
29
|
|
243
|
-
|
244
|
-
|
245
|
-
end
|
30
|
+
context 'with redis data store', :redis do
|
31
|
+
let(:data_store) { Stoplight::DataStore::Redis.new(redis) }
|
246
32
|
|
247
|
-
|
248
|
-
|
249
|
-
expect(e).to eql(nil)
|
250
|
-
fallback_result
|
251
|
-
end
|
252
|
-
expect(subject.run).to eql(fallback_result)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
33
|
+
it_behaves_like 'Stoplight::Light::Runnable#color'
|
34
|
+
it_behaves_like 'Stoplight::Light::Runnable#run'
|
256
35
|
end
|
257
36
|
end
|