stoplight 0.1.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.
@@ -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