stoplight 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,190 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'fakeredis'
5
+
6
+ describe Stoplight::DataStore::Redis do
7
+ let(:error) { error_class.new }
8
+ let(:error_class) { Class.new(StandardError) }
9
+ let(:name) { SecureRandom.hex }
10
+ let(:state) { Stoplight::DataStore::STATES.to_a.sample }
11
+ let(:threshold) { rand(10) }
12
+
13
+ subject(:data_store) { described_class.new }
14
+
15
+ describe '#attempts' do
16
+ subject(:result) { data_store.attempts(name) }
17
+
18
+ it 'returns 0' do
19
+ expect(result).to eql(0)
20
+ end
21
+
22
+ context 'with an attempt' do
23
+ before { data_store.record_attempt(name) }
24
+
25
+ it 'returns 1' do
26
+ expect(result).to eql(1)
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '#clear_attempts' do
32
+ subject(:result) { data_store.clear_attempts(name) }
33
+
34
+ it 'returns 0' do
35
+ expect(result).to eql(0)
36
+ end
37
+
38
+ context 'with an attempt' do
39
+ before { data_store.record_attempt(name) }
40
+
41
+ it 'returns 1' do
42
+ expect(result).to eql(1)
43
+ end
44
+
45
+ it 'clears the attempts' do
46
+ result
47
+ expect(data_store.attempts(name)).to eql(0)
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#clear_failures' do
53
+ subject(:result) { data_store.clear_failures(name) }
54
+
55
+ it 'returns 0' do
56
+ expect(result).to eql(0)
57
+ end
58
+
59
+ context 'with a failure' do
60
+ before { data_store.record_failure(name, error) }
61
+
62
+ it 'returns 1' do
63
+ expect(result).to eql(1)
64
+ end
65
+
66
+ it 'clears the failures' do
67
+ result
68
+ expect(data_store.failures(name)).to be_empty
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#failures' do
74
+ subject(:result) { data_store.failures(name) }
75
+
76
+ it 'returns an empty array' do
77
+ expect(result).to be_an(Array)
78
+ expect(result).to be_empty
79
+ end
80
+
81
+ context 'with a failure' do
82
+ before { data_store.record_failure(name, error) }
83
+
84
+ it 'returns a non-empty array' do
85
+ expect(result).to be_an(Array)
86
+ expect(result).to_not be_empty
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '#names' do
92
+ subject(:result) { data_store.names }
93
+
94
+ it 'returns an array' do
95
+ expect(result).to be_an(Array)
96
+ end
97
+
98
+ context 'with a name' do
99
+ before { data_store.set_threshold(name, threshold) }
100
+
101
+ it 'includes the name' do
102
+ expect(result).to include(name)
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#record_attempt' do
108
+ subject(:result) { data_store.record_attempt(name) }
109
+
110
+ it 'returns 1' do
111
+ expect(result).to eql(1)
112
+ end
113
+
114
+ it 'records the attempt' do
115
+ result
116
+ expect(data_store.attempts(name)).to eql(1)
117
+ end
118
+ end
119
+
120
+ describe '#record_failure' do
121
+ subject(:result) { data_store.record_failure(name, error) }
122
+
123
+ it 'returns 1' do
124
+ expect(result).to eql(1)
125
+ end
126
+
127
+ it 'records the failure' do
128
+ result
129
+ expect(data_store.failures(name)).to_not be_empty
130
+ end
131
+ end
132
+
133
+ describe '#set_state' do
134
+ subject(:result) { data_store.set_state(name, state) }
135
+
136
+ it 'returns the state' do
137
+ expect(result).to eql(state)
138
+ end
139
+
140
+ it 'sets the state' do
141
+ result
142
+ expect(data_store.state(name)).to eql(state)
143
+ end
144
+ end
145
+
146
+ describe '#set_threshold' do
147
+ subject(:result) { data_store.set_threshold(name, threshold) }
148
+
149
+ it 'returns the threshold' do
150
+ expect(result).to eql(threshold)
151
+ end
152
+
153
+ it 'sets the threshold' do
154
+ result
155
+ expect(data_store.threshold(name)).to eql(threshold)
156
+ end
157
+ end
158
+
159
+ describe '#state' do
160
+ subject(:result) { data_store.state(name) }
161
+
162
+ it 'returns the default state' do
163
+ expect(result).to eql(Stoplight::DataStore::STATE_UNLOCKED)
164
+ end
165
+
166
+ context 'with a state' do
167
+ before { data_store.set_state(name, state) }
168
+
169
+ it 'returns the state' do
170
+ expect(result).to eql(state)
171
+ end
172
+ end
173
+ end
174
+
175
+ describe '#threshold' do
176
+ subject(:result) { data_store.threshold(name) }
177
+
178
+ it 'returns nil' do
179
+ expect(result).to be(nil)
180
+ end
181
+
182
+ context 'with a threshold' do
183
+ before { data_store.set_threshold(name, threshold) }
184
+
185
+ it 'returns the threshold' do
186
+ expect(result).to eql(threshold)
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Stoplight::Failure do
6
+ let(:error) { double }
7
+
8
+ subject(:failure) { described_class.new(error) }
9
+
10
+ describe '#to_json' do
11
+ let(:json) { JSON.parse(result) }
12
+
13
+ subject(:result) { failure.to_json }
14
+
15
+ it 'includes the error' do
16
+ expect(json['error']).to eql(error.inspect)
17
+ end
18
+
19
+ it 'includes the time' do
20
+ expect(json['time']).to eql(Time.now.to_s)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,222 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Stoplight::Light do
6
+ let(:code) { proc { code_result } }
7
+ let(:code_result) { double }
8
+ let(:name) { SecureRandom.hex }
9
+
10
+ subject(:light) { described_class.new(name, &code) }
11
+
12
+ describe '#initialize' do
13
+ it 'uses the default allowed errors' do
14
+ expect(light.allowed_errors).to eql([])
15
+ end
16
+
17
+ it 'sets the code' do
18
+ expect(light.code).to eql(code.to_proc)
19
+ end
20
+
21
+ it 'sets the name' do
22
+ expect(light.name).to eql(name.to_s)
23
+ end
24
+ end
25
+
26
+ describe '#run' do
27
+ subject(:result) { light.run }
28
+
29
+ it 'syncs settings' do
30
+ expect(Stoplight.data_store.threshold(name)).to be nil
31
+ result
32
+ expect(Stoplight.data_store.threshold(name)).to eql(
33
+ light.threshold)
34
+ end
35
+
36
+ context 'green' do
37
+ before { allow(light).to receive(:green?).and_return(true) }
38
+
39
+ it 'runs the code' do
40
+ expect(result).to eql(code_result)
41
+ end
42
+
43
+ it 'clears failures' do
44
+ Stoplight.record_failure(name, nil)
45
+ expect(Stoplight.failures(name)).to_not be_empty
46
+ result
47
+ expect(Stoplight.failures(name)).to be_empty
48
+ end
49
+
50
+ context 'with failing code' do
51
+ let(:code_result) { fail error }
52
+ let(:error) { klass.new }
53
+ let(:klass) { Class.new(StandardError) }
54
+ let(:safe_result) do
55
+ begin
56
+ result
57
+ rescue klass
58
+ nil
59
+ end
60
+ end
61
+
62
+ it 'raises the error' do
63
+ expect { result }.to raise_error(error)
64
+ end
65
+
66
+ it 'records the failure' do
67
+ expect(Stoplight.failures(name)).to be_empty
68
+ safe_result
69
+ expect(Stoplight.failures(name)).to_not be_empty
70
+ end
71
+
72
+ context do
73
+ before { light.with_allowed_errors([klass]) }
74
+
75
+ it 'clears failures' do
76
+ Stoplight.record_failure(name, nil)
77
+ expect(Stoplight.failures(name)).to_not be_empty
78
+ safe_result
79
+ expect(Stoplight.failures(name)).to be_empty
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'not green' do
86
+ let(:fallback) { proc { fallback_result } }
87
+ let(:fallback_result) { double }
88
+
89
+ before do
90
+ light.with_fallback(&fallback)
91
+ allow(light).to receive(:green?).and_return(false)
92
+ end
93
+
94
+ it 'runs the fallback' do
95
+ expect(result).to eql(fallback_result)
96
+ end
97
+
98
+ it 'records the attempt' do
99
+ result
100
+ expect(Stoplight.data_store.attempts(name)).to eql(1)
101
+ end
102
+ end
103
+ end
104
+
105
+ describe '#with_allowed_errors' do
106
+ let(:allowed_errors) { [double] }
107
+
108
+ subject(:result) { light.with_allowed_errors(allowed_errors) }
109
+
110
+ it 'returns self' do
111
+ expect(result).to be light
112
+ end
113
+
114
+ it 'sets the allowed errors' do
115
+ expect(result.allowed_errors).to eql(allowed_errors)
116
+ end
117
+ end
118
+
119
+ describe '#with_fallback' do
120
+ let(:fallback) { proc { fallback_result } }
121
+ let(:fallback_result) { double }
122
+
123
+ subject(:result) { light.with_fallback(&fallback) }
124
+
125
+ it 'returns self' do
126
+ expect(result).to be light
127
+ end
128
+
129
+ it 'sets the fallback' do
130
+ expect(result.fallback).to eql(fallback)
131
+ end
132
+ end
133
+
134
+ describe '#with_threshold' do
135
+ let(:threshold) { rand(10) }
136
+
137
+ subject(:result) { light.with_threshold(threshold) }
138
+
139
+ it 'returns self' do
140
+ expect(result).to be light
141
+ end
142
+
143
+ it 'sets the threshold' do
144
+ expect(result.threshold).to eql(threshold)
145
+ end
146
+ end
147
+
148
+ describe '#fallback' do
149
+ subject(:result) { light.fallback }
150
+
151
+ it 'uses the default fallback' do
152
+ expect { result }.to raise_error(Stoplight::Error::NoFallback)
153
+ end
154
+ end
155
+
156
+ describe '#green?' do
157
+ subject(:result) { light.green? }
158
+
159
+ it 'is true' do
160
+ expect(result).to be true
161
+ end
162
+
163
+ context 'locked green' do
164
+ before do
165
+ Stoplight.set_state(name, Stoplight::DataStore::STATE_LOCKED_GREEN)
166
+ end
167
+
168
+ it 'is true' do
169
+ expect(result).to be true
170
+ end
171
+ end
172
+
173
+ context 'locked red' do
174
+ before do
175
+ Stoplight.set_state(name, Stoplight::DataStore::STATE_LOCKED_RED)
176
+ end
177
+
178
+ it 'is false' do
179
+ expect(result).to be false
180
+ end
181
+ end
182
+
183
+ context 'with failures' do
184
+ before do
185
+ light.threshold.times { Stoplight.record_failure(name, nil) }
186
+ end
187
+
188
+ it 'is false' do
189
+ expect(result).to be false
190
+ end
191
+ end
192
+ end
193
+
194
+ describe '#red?' do
195
+ subject(:result) { light.red? }
196
+
197
+ context 'green' do
198
+ before { allow(light).to receive(:green?).and_return(true) }
199
+
200
+ it 'is false' do
201
+ expect(result).to be false
202
+ end
203
+ end
204
+
205
+ context 'not green' do
206
+ before { allow(light).to receive(:green?).and_return(false) }
207
+
208
+ it 'is true' do
209
+ expect(result).to be true
210
+ end
211
+ end
212
+ end
213
+
214
+ describe '#threshold' do
215
+ subject(:result) { light.threshold }
216
+
217
+ it 'uses the default threshold' do
218
+ expect(result).to eql(
219
+ described_class.const_get(:DEFAULT_THRESHOLD))
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,128 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Stoplight do
6
+ let(:name) { SecureRandom.hex }
7
+
8
+ it 'forwards all data store methods' do
9
+ (Stoplight::DataStore::Base.new.methods - Object.methods).each do |method|
10
+ expect(Stoplight).to respond_to(method)
11
+ end
12
+ end
13
+
14
+ describe '::VERSION' do
15
+ subject(:result) { described_class.const_get(:VERSION) }
16
+
17
+ it 'is a Gem::Version' do
18
+ expect(result).to be_a(Gem::Version)
19
+ end
20
+ end
21
+
22
+ describe '.data_store' do
23
+ subject(:result) { described_class.data_store }
24
+
25
+ it 'uses the default data store' do
26
+ expect(result).to be_a(Stoplight::DataStore::Memory)
27
+ end
28
+
29
+ it 'memoizes the result' do
30
+ expect(result).to be described_class.data_store
31
+ end
32
+
33
+ context 'with a custom data store' do
34
+ let(:data_store) { double }
35
+
36
+ before do
37
+ @data_store = described_class.data_store
38
+ described_class.data_store(data_store)
39
+ end
40
+
41
+ after { described_class.data_store(@data_store) }
42
+
43
+ it 'returns the data store' do
44
+ expect(result).to eql(data_store)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '.green?' do
50
+ subject(:result) { described_class.green?(name) }
51
+
52
+ it 'is true' do
53
+ expect(result).to be true
54
+ end
55
+
56
+ context 'locked green' do
57
+ before do
58
+ described_class.set_state(
59
+ name, Stoplight::DataStore::STATE_LOCKED_GREEN)
60
+ end
61
+
62
+ it 'is true' do
63
+ expect(result).to be true
64
+ end
65
+ end
66
+
67
+ context 'locked red' do
68
+ before do
69
+ described_class.set_state(
70
+ name, Stoplight::DataStore::STATE_LOCKED_RED)
71
+ end
72
+
73
+ it 'is false' do
74
+ expect(result).to be false
75
+ end
76
+ end
77
+
78
+ context 'with failures' do
79
+ before do
80
+ described_class.threshold(name).times do
81
+ described_class.record_failure(name, nil)
82
+ end
83
+ end
84
+
85
+ it 'is false' do
86
+ expect(result).to be false
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '.red?' do
92
+ subject(:result) { described_class.red?(name) }
93
+
94
+ context 'green' do
95
+ before { allow(described_class).to receive(:green?).and_return(true) }
96
+
97
+ it 'is false' do
98
+ expect(result).to be false
99
+ end
100
+ end
101
+
102
+ context 'not green' do
103
+ before { allow(described_class).to receive(:green?).and_return(false) }
104
+
105
+ it 'is true' do
106
+ expect(result).to be true
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '.threshold' do
112
+ subject(:result) { described_class.threshold(name) }
113
+
114
+ it 'uses the default threshold' do
115
+ expect(result).to eql(Stoplight::Light::DEFAULT_THRESHOLD)
116
+ end
117
+
118
+ context 'with a custom threshold' do
119
+ let(:threshold) { rand(10) }
120
+
121
+ before { described_class.set_threshold(name, threshold) }
122
+
123
+ it 'uses the threshold' do
124
+ expect(result).to eql(threshold)
125
+ end
126
+ end
127
+ end
128
+ end