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
@@ -7,153 +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
|
133
|
-
|
134
|
-
describe '#with_notification_lock' do
|
135
|
-
context 'when notification is already sent' do
|
136
|
-
before do
|
137
|
-
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
|
138
|
-
end
|
139
|
-
|
140
|
-
it 'does not yield passed block' do
|
141
|
-
expect do |b|
|
142
|
-
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED, &b)
|
143
|
-
end.not_to yield_control
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
context 'when notification is not already sent' do
|
148
|
-
before do
|
149
|
-
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'yields passed block' do
|
153
|
-
expect do |b|
|
154
|
-
data_store.with_notification_lock(light, Stoplight::Color::RED, Stoplight::Color::GREEN, &b)
|
155
|
-
end.to yield_control
|
156
|
-
end
|
157
|
-
end
|
158
|
-
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'
|
159
22
|
end
|
@@ -1,177 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
|
-
require 'mock_redis'
|
5
4
|
|
6
|
-
RSpec.describe Stoplight::DataStore::Redis do
|
5
|
+
RSpec.describe Stoplight::DataStore::Redis, :redis do
|
7
6
|
let(:data_store) { described_class.new(redis, redlock: redlock) }
|
8
|
-
let(:redis) { MockRedis.new }
|
9
7
|
let(:redlock) { instance_double(Redlock::Client) }
|
10
8
|
let(:light) { Stoplight::Light.new(name) {} }
|
11
9
|
let(:name) { ('a'..'z').to_a.shuffle.join }
|
12
|
-
let(:failure) { Stoplight::Failure.new('class', 'message', Time.new) }
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
expect(data_store.names).to eql([light.name])
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'contains the name of a light with a set state' do
|
33
|
-
data_store.set_state(light, Stoplight::State::UNLOCKED)
|
34
|
-
expect(data_store.names).to eql([light.name])
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'does not duplicate names' do
|
38
|
-
data_store.record_failure(light, failure)
|
39
|
-
data_store.set_state(light, Stoplight::State::UNLOCKED)
|
40
|
-
expect(data_store.names).to eql([light.name])
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'supports names containing colons' do
|
44
|
-
light = Stoplight::Light.new('http://api.example.com/some/action')
|
45
|
-
data_store.record_failure(light, failure)
|
46
|
-
expect(data_store.names).to eql([light.name])
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe '#get_all' do
|
51
|
-
it 'returns the failures and the state' do
|
52
|
-
failures, state = data_store.get_all(light)
|
53
|
-
expect(failures).to eql([])
|
54
|
-
expect(state).to eql(Stoplight::State::UNLOCKED)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe '#get_failures' do
|
59
|
-
it 'is initially empty' do
|
60
|
-
expect(data_store.get_failures(light)).to eql([])
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'handles invalid JSON' do
|
64
|
-
expect(redis.keys.size).to eql(0)
|
65
|
-
data_store.record_failure(light, failure)
|
66
|
-
expect(redis.keys.size).to eql(1)
|
67
|
-
redis.lset(redis.keys.first, 0, 'invalid JSON')
|
68
|
-
light.with_error_notifier { |_error| }
|
69
|
-
expect(data_store.get_failures(light).size).to eql(1)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
describe '#record_failure' do
|
74
|
-
it 'returns the number of failures' do
|
75
|
-
expect(data_store.record_failure(light, failure)).to eql(1)
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'persists the failure' do
|
79
|
-
data_store.record_failure(light, failure)
|
80
|
-
expect(data_store.get_failures(light)).to eq([failure])
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'stores more recent failures at the head' do
|
84
|
-
data_store.record_failure(light, failure)
|
85
|
-
other = Stoplight::Failure.new('class', 'message 2', Time.new)
|
86
|
-
data_store.record_failure(light, other)
|
87
|
-
expect(data_store.get_failures(light)).to eq([other, failure])
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'limits the number of stored failures' do
|
91
|
-
light.with_threshold(1)
|
92
|
-
data_store.record_failure(light, failure)
|
93
|
-
other = Stoplight::Failure.new('class', 'message 2', Time.new)
|
94
|
-
data_store.record_failure(light, other)
|
95
|
-
expect(data_store.get_failures(light)).to eq([other])
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
describe '#clear_failures' do
|
100
|
-
it 'returns the failures' do
|
101
|
-
data_store.record_failure(light, failure)
|
102
|
-
expect(data_store.clear_failures(light)).to eq([failure])
|
103
|
-
end
|
104
|
-
|
105
|
-
it 'clears the failures' do
|
106
|
-
data_store.record_failure(light, failure)
|
107
|
-
data_store.clear_failures(light)
|
108
|
-
expect(data_store.get_failures(light)).to eql([])
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
describe '#get_state' do
|
113
|
-
it 'is initially unlocked' do
|
114
|
-
expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
describe '#set_state' do
|
119
|
-
it 'returns the state' do
|
120
|
-
state = 'state'
|
121
|
-
expect(data_store.set_state(light, state)).to eql(state)
|
122
|
-
end
|
123
|
-
|
124
|
-
it 'persists the state' do
|
125
|
-
state = 'state'
|
126
|
-
data_store.set_state(light, state)
|
127
|
-
expect(data_store.get_state(light)).to eql(state)
|
128
|
-
end
|
129
|
-
end
|
10
|
+
let(:failure) { Stoplight::Failure.new('class', 'message', Time.new - 60) }
|
11
|
+
let(:other) { Stoplight::Failure.new('class', 'message 2', Time.new) }
|
12
|
+
|
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'
|
21
|
+
|
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
|
130
27
|
|
131
|
-
|
132
|
-
|
133
|
-
state = 'state'
|
134
|
-
data_store.set_state(light, state)
|
135
|
-
expect(data_store.clear_state(light)).to eql(state)
|
136
|
-
end
|
28
|
+
it 'handles it without an error' do
|
29
|
+
expect(failure).to receive(:to_json).and_return('invalid JSON')
|
137
30
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
|
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
|
143
35
|
end
|
144
36
|
end
|
145
37
|
|
146
|
-
|
147
|
-
let(:lock_key) { "stoplight:notification_lock:#{name}" }
|
38
|
+
it_behaves_like 'Stoplight::DataStore::Base#with_notification_lock' do
|
39
|
+
let(:lock_key) { "stoplight:v4:notification_lock:#{name}" }
|
148
40
|
|
149
41
|
before do
|
150
42
|
allow(redlock).to receive(:lock).with(lock_key, 2_000).and_yield
|
151
43
|
end
|
152
|
-
|
153
|
-
context 'when notification is already sent' do
|
154
|
-
before do
|
155
|
-
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
|
156
|
-
end
|
157
|
-
|
158
|
-
it 'does not yield passed block' do
|
159
|
-
expect do |b|
|
160
|
-
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED, &b)
|
161
|
-
end.not_to yield_control
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
context 'when notification is not already sent' do
|
166
|
-
before do
|
167
|
-
data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
|
168
|
-
end
|
169
|
-
|
170
|
-
it 'yields passed block' do
|
171
|
-
expect do |b|
|
172
|
-
data_store.with_notification_lock(light, Stoplight::Color::RED, Stoplight::Color::GREEN, &b)
|
173
|
-
end.to yield_control
|
174
|
-
end
|
175
|
-
end
|
176
44
|
end
|
177
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
|