berater 0.6.1 → 0.9.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/lib/berater.rb +29 -15
- data/lib/berater/concurrency_limiter.rb +16 -23
- data/lib/berater/dsl.rb +8 -8
- data/lib/berater/inhibitor.rb +4 -4
- data/lib/berater/limiter.rb +55 -23
- data/lib/berater/limiter_set.rb +66 -0
- data/lib/berater/lock.rb +2 -1
- data/lib/berater/rate_limiter.rb +39 -31
- data/lib/berater/rspec.rb +1 -2
- data/lib/berater/rspec/matchers.rb +11 -31
- data/lib/berater/static_limiter.rb +49 -0
- data/lib/berater/test_mode.rb +27 -23
- data/lib/berater/unlimiter.rb +4 -0
- data/lib/berater/utils.rb +9 -0
- data/lib/berater/version.rb +1 -1
- data/spec/berater_spec.rb +38 -70
- data/spec/concurrency_limiter_spec.rb +59 -53
- data/spec/dsl_refinement_spec.rb +0 -12
- data/spec/dsl_spec.rb +5 -17
- data/spec/inhibitor_spec.rb +10 -5
- data/spec/limiter_set_spec.rb +173 -0
- data/spec/limiter_spec.rb +125 -10
- data/spec/matchers_spec.rb +21 -85
- data/spec/middleware_spec.rb +110 -0
- data/spec/rate_limiter_spec.rb +88 -38
- data/spec/riddle_spec.rb +6 -2
- data/spec/static_limiter_spec.rb +79 -0
- data/spec/test_mode_spec.rb +32 -109
- data/spec/unlimiter_spec.rb +11 -5
- metadata +24 -2
@@ -0,0 +1,110 @@
|
|
1
|
+
class Meddler
|
2
|
+
def call(*)
|
3
|
+
yield
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'Berater.middleware' do
|
8
|
+
subject { Berater.middleware }
|
9
|
+
|
10
|
+
describe 'adding middleware' do
|
11
|
+
after { is_expected.to include Meddler }
|
12
|
+
|
13
|
+
it 'can be done inline' do
|
14
|
+
Berater.middleware.use Meddler
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can be done with a block' do
|
18
|
+
Berater.middleware do
|
19
|
+
use Meddler
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'resets along with Berater' do
|
25
|
+
Berater.middleware.use Meddler
|
26
|
+
is_expected.to include Meddler
|
27
|
+
|
28
|
+
Berater.reset
|
29
|
+
is_expected.to be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'Berater::Limiter#limit' do
|
33
|
+
let(:middleware) { Meddler.new }
|
34
|
+
let(:limiter) { Berater::ConcurrencyLimiter.new(:key, 1) }
|
35
|
+
|
36
|
+
before do
|
37
|
+
expect(Meddler).to receive(:new).and_return(middleware).at_least(1)
|
38
|
+
Berater.middleware.use Meddler
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'calls the middleware' do
|
42
|
+
expect(middleware).to receive(:call)
|
43
|
+
limiter.limit
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'calls the middleware, passing the limiter and options' do
|
47
|
+
expect(middleware).to receive(:call).with(
|
48
|
+
limiter,
|
49
|
+
hash_including(:capacity, :cost)
|
50
|
+
)
|
51
|
+
|
52
|
+
limiter.limit
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when used per ususual' do
|
56
|
+
before do
|
57
|
+
expect(middleware).to receive(:call).and_call_original.at_least(1)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'still works inline' do
|
61
|
+
expect(limiter.limit).to be_a Berater::Lock
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'still works in block mode' do
|
65
|
+
expect(limiter.limit { 123 }).to be 123
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'still has limits' do
|
69
|
+
limiter.limit
|
70
|
+
expect(limiter).to be_overloaded
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when middleware meddles' do
|
75
|
+
it 'can change the capacity' do
|
76
|
+
expect(middleware).to receive(:call) do |limiter, opts, &block|
|
77
|
+
opts[:capacity] = 0
|
78
|
+
block.call
|
79
|
+
end
|
80
|
+
|
81
|
+
expect { limiter.limit }.to be_overloaded
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'can change the cost' do
|
85
|
+
expect(middleware).to receive(:call) do |limiter, opts, &block|
|
86
|
+
opts[:cost] = 2
|
87
|
+
block.call
|
88
|
+
end
|
89
|
+
|
90
|
+
expect { limiter.limit }.to be_overloaded
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'can change the limiter' do
|
94
|
+
other_limiter = Berater::Inhibitor.new
|
95
|
+
|
96
|
+
expect(middleware).to receive(:call) do |limiter, opts, &block|
|
97
|
+
block.call other_limiter, opts
|
98
|
+
end
|
99
|
+
expect(other_limiter).to receive(:acquire_lock).and_call_original
|
100
|
+
|
101
|
+
expect { limiter.limit }.to be_overloaded
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'can abort by not yielding' do
|
105
|
+
expect(middleware).to receive(:call)
|
106
|
+
expect(limiter.limit).to be nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/spec/rate_limiter_spec.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
describe Berater::RateLimiter do
|
2
|
-
it_behaves_like 'a limiter',
|
2
|
+
it_behaves_like 'a limiter', described_class.new(:key, 3, :second)
|
3
|
+
it_behaves_like 'a limiter', described_class.new(:key, 3.5, :second)
|
3
4
|
|
4
5
|
describe '.new' do
|
5
6
|
let(:limiter) { described_class.new(:key, 1, :second) }
|
@@ -36,6 +37,7 @@ describe Berater::RateLimiter do
|
|
36
37
|
it { expect_bad_capacity(-1) }
|
37
38
|
it { expect_bad_capacity('1') }
|
38
39
|
it { expect_bad_capacity(:one) }
|
40
|
+
it { expect_bad_capacity(Float::INFINITY) }
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
@@ -46,7 +48,7 @@ describe Berater::RateLimiter do
|
|
46
48
|
|
47
49
|
it 'saves the interval in original and millisecond format' do
|
48
50
|
expect(subject.interval).to be :second
|
49
|
-
expect(subject.instance_variable_get(:@
|
51
|
+
expect(subject.instance_variable_get(:@interval)).to be 10**3
|
50
52
|
end
|
51
53
|
|
52
54
|
it 'must be > 0' do
|
@@ -75,42 +77,42 @@ describe Berater::RateLimiter do
|
|
75
77
|
it 'limits excessive calls' do
|
76
78
|
3.times { limiter.limit }
|
77
79
|
|
78
|
-
expect(limiter).to
|
80
|
+
expect(limiter).to be_overloaded
|
79
81
|
end
|
80
82
|
|
81
83
|
it 'resets limit over time' do
|
82
84
|
3.times { limiter.limit }
|
83
|
-
expect(limiter).to
|
85
|
+
expect(limiter).to be_overloaded
|
84
86
|
|
85
87
|
Timecop.freeze(1)
|
86
88
|
|
87
89
|
3.times { limiter.limit }
|
88
|
-
expect(limiter).to
|
90
|
+
expect(limiter).to be_overloaded
|
89
91
|
end
|
90
92
|
|
91
93
|
context 'with millisecond precision' do
|
92
94
|
it 'resets limit over time' do
|
93
95
|
3.times { limiter.limit }
|
94
|
-
expect(limiter).to
|
96
|
+
expect(limiter).to be_overloaded
|
95
97
|
|
96
98
|
# travel forward to just before the count decrements
|
97
99
|
Timecop.freeze(0.333)
|
98
|
-
expect(limiter).to
|
100
|
+
expect(limiter).to be_overloaded
|
99
101
|
|
100
102
|
# traveling one more millisecond will decrement the count
|
101
103
|
Timecop.freeze(0.001)
|
102
104
|
limiter.limit
|
103
|
-
expect(limiter).to
|
105
|
+
expect(limiter).to be_overloaded
|
104
106
|
end
|
105
107
|
|
106
108
|
it 'works when drip rate is < 1 per millisecond' do
|
107
109
|
limiter = described_class.new(:key, 2_000, :second)
|
108
110
|
|
109
111
|
limiter.capacity.times { limiter.limit }
|
110
|
-
expect(limiter).to
|
112
|
+
expect(limiter).to be_overloaded
|
111
113
|
|
112
114
|
Timecop.freeze(0.001)
|
113
|
-
expect(limiter).not_to
|
115
|
+
expect(limiter).not_to be_overloaded
|
114
116
|
|
115
117
|
2.times { limiter.limit }
|
116
118
|
end
|
@@ -121,9 +123,9 @@ describe Berater::RateLimiter do
|
|
121
123
|
|
122
124
|
it 'still works' do
|
123
125
|
limiter.limit
|
124
|
-
expect(limiter).not_to
|
126
|
+
expect(limiter).not_to be_overloaded
|
125
127
|
|
126
|
-
expect { limiter.limit }.to
|
128
|
+
expect { limiter.limit }.to be_overloaded
|
127
129
|
|
128
130
|
limiter.limit(cost: 0.5)
|
129
131
|
end
|
@@ -132,30 +134,72 @@ describe Berater::RateLimiter do
|
|
132
134
|
it 'accepts a dynamic capacity' do
|
133
135
|
limiter = described_class.new(:key, 1, :second)
|
134
136
|
|
135
|
-
expect { limiter.limit(capacity: 0) }.to
|
137
|
+
expect { limiter.limit(capacity: 0) }.to be_overloaded
|
136
138
|
5.times { limiter.limit(capacity: 10) }
|
137
|
-
expect { limiter }.to
|
139
|
+
expect { limiter }.to be_overloaded
|
138
140
|
end
|
139
141
|
|
140
142
|
context 'works with cost parameter' do
|
141
|
-
it { expect { limiter.limit(cost: 4) }.to
|
143
|
+
it { expect { limiter.limit(cost: 4) }.to be_overloaded }
|
142
144
|
|
143
145
|
it 'works within limit' do
|
144
146
|
limiter.limit(cost: 3)
|
145
|
-
expect { limiter.limit }.to
|
147
|
+
expect { limiter.limit }.to be_overloaded
|
146
148
|
end
|
147
149
|
|
148
150
|
it 'resets over time' do
|
149
151
|
limiter.limit(cost: 3)
|
150
|
-
expect(limiter).to
|
152
|
+
expect(limiter).to be_overloaded
|
151
153
|
|
152
154
|
Timecop.freeze(1)
|
153
|
-
expect(limiter).not_to
|
155
|
+
expect(limiter).not_to be_overloaded
|
154
156
|
end
|
155
157
|
|
156
|
-
|
157
|
-
|
158
|
-
|
158
|
+
context 'when cost is a Float' do
|
159
|
+
it 'still works' do
|
160
|
+
2.times { limiter.limit(cost: 1.5) }
|
161
|
+
expect(limiter).to be_overloaded
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'calculates contention correctly' do
|
165
|
+
# note: Redis must return Floats as strings to maintain precision
|
166
|
+
lock = limiter.limit(cost: 1.5)
|
167
|
+
expect(lock.contention).to be 1.5
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'with clock skew' do
|
173
|
+
let(:limiter) { described_class.new(:key, 10, :second) }
|
174
|
+
|
175
|
+
it 'works skewing backward' do
|
176
|
+
limiter.limit(cost: 9)
|
177
|
+
|
178
|
+
Timecop.freeze(-0.1) do
|
179
|
+
limiter.limit
|
180
|
+
expect(limiter).to be_overloaded
|
181
|
+
end
|
182
|
+
|
183
|
+
expect(limiter).to be_overloaded
|
184
|
+
|
185
|
+
Timecop.freeze(0.1)
|
186
|
+
limiter.limit
|
187
|
+
expect(limiter).to be_overloaded
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'works skewing forward' do
|
191
|
+
limiter.limit
|
192
|
+
|
193
|
+
Timecop.freeze(0.1) do
|
194
|
+
# one drip later
|
195
|
+
limiter.limit(cost: 10)
|
196
|
+
expect(limiter).to be_overloaded
|
197
|
+
end
|
198
|
+
|
199
|
+
expect(limiter).to be_overloaded
|
200
|
+
|
201
|
+
Timecop.freeze(0.1)
|
202
|
+
expect(limiter).to be_overloaded
|
159
203
|
end
|
160
204
|
end
|
161
205
|
|
@@ -164,10 +208,10 @@ describe Berater::RateLimiter do
|
|
164
208
|
let(:limiter_two) { described_class.new(:key, 1, :second) }
|
165
209
|
|
166
210
|
it 'works as expected' do
|
167
|
-
expect(limiter_one.limit).not_to
|
211
|
+
expect(limiter_one.limit).not_to be_overloaded
|
168
212
|
|
169
|
-
expect(limiter_one).to
|
170
|
-
expect(limiter_two).to
|
213
|
+
expect(limiter_one).to be_overloaded
|
214
|
+
expect(limiter_two).to be_overloaded
|
171
215
|
end
|
172
216
|
end
|
173
217
|
|
@@ -176,27 +220,33 @@ describe Berater::RateLimiter do
|
|
176
220
|
let(:limiter_two) { described_class.new(:two, 2, :second) }
|
177
221
|
|
178
222
|
it 'works as expected' do
|
179
|
-
expect(limiter_one.limit).not_to
|
180
|
-
expect(limiter_two.limit).not_to
|
223
|
+
expect(limiter_one.limit).not_to be_overloaded
|
224
|
+
expect(limiter_two.limit).not_to be_overloaded
|
181
225
|
|
182
|
-
expect(limiter_one).to
|
183
|
-
expect(limiter_two.limit).not_to
|
226
|
+
expect(limiter_one).to be_overloaded
|
227
|
+
expect(limiter_two.limit).not_to be_overloaded
|
184
228
|
|
185
|
-
expect(limiter_one).to
|
186
|
-
expect(limiter_two).to
|
229
|
+
expect(limiter_one).to be_overloaded
|
230
|
+
expect(limiter_two).to be_overloaded
|
187
231
|
end
|
188
232
|
end
|
189
233
|
end
|
190
234
|
|
191
|
-
describe '#
|
192
|
-
let(:limiter) { described_class.new(:key,
|
235
|
+
describe '#utilization' do
|
236
|
+
let(:limiter) { described_class.new(:key, 10, :minute) }
|
193
237
|
|
194
|
-
it
|
195
|
-
expect(limiter.
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
238
|
+
it do
|
239
|
+
expect(limiter.utilization).to be 0.0
|
240
|
+
|
241
|
+
2.times { limiter.limit }
|
242
|
+
expect(limiter.utilization).to be 0.2
|
243
|
+
|
244
|
+
8.times { limiter.limit }
|
245
|
+
expect(limiter.utilization).to be 1.0
|
246
|
+
|
247
|
+
Timecop.freeze(30)
|
248
|
+
|
249
|
+
expect(limiter.utilization).to be 0.5
|
200
250
|
end
|
201
251
|
end
|
202
252
|
|
data/spec/riddle_spec.rb
CHANGED
@@ -42,11 +42,15 @@ class ConcurrenyRiddler
|
|
42
42
|
timeout ||= 1_000 # fake infinity
|
43
43
|
|
44
44
|
limiter = Berater::RateLimiter.new(:key, capacity, timeout)
|
45
|
-
limiter.limit
|
45
|
+
lock = limiter.limit
|
46
46
|
yield if block_given?
|
47
47
|
ensure
|
48
48
|
# decrement counter
|
49
|
-
|
49
|
+
if lock
|
50
|
+
key = limiter.send(:cache_key)
|
51
|
+
count, ts = limiter.redis.get(key).split ';'
|
52
|
+
limiter.redis.set(key, "#{count.to_f - 1};#{ts}")
|
53
|
+
end
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
describe Berater::StaticLimiter do
|
2
|
+
it_behaves_like 'a limiter', described_class.new(:key, 3)
|
3
|
+
it_behaves_like 'a limiter', described_class.new(:key, 3.5)
|
4
|
+
|
5
|
+
describe '#limit' do
|
6
|
+
let(:limiter) { described_class.new(:key, 3) }
|
7
|
+
|
8
|
+
it 'limits excessive calls' do
|
9
|
+
3.times { limiter.limit }
|
10
|
+
|
11
|
+
expect(limiter).to be_overloaded
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when capacity is a Float' do
|
15
|
+
let(:limiter) { described_class.new(:key, 1.5) }
|
16
|
+
|
17
|
+
it 'still works' do
|
18
|
+
limiter.limit
|
19
|
+
expect(limiter).not_to be_overloaded
|
20
|
+
|
21
|
+
expect { limiter.limit }.to be_overloaded
|
22
|
+
|
23
|
+
limiter.limit(cost: 0.5)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'accepts a dynamic capacity' do
|
28
|
+
limiter = described_class.new(:key, 1)
|
29
|
+
|
30
|
+
expect { limiter.limit(capacity: 0) }.to be_overloaded
|
31
|
+
5.times { limiter.limit(capacity: 10) }
|
32
|
+
expect { limiter }.to be_overloaded
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'works with cost parameter' do
|
36
|
+
let(:limiter) { described_class.new(:key, 3) }
|
37
|
+
|
38
|
+
it { expect { limiter.limit(cost: 4) }.to be_overloaded }
|
39
|
+
|
40
|
+
it 'works within limit' do
|
41
|
+
limiter.limit(cost: 3)
|
42
|
+
expect { limiter.limit }.to be_overloaded
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when cost is a Float' do
|
46
|
+
it 'still works' do
|
47
|
+
2.times { limiter.limit(cost: 1.5) }
|
48
|
+
expect(limiter).to be_overloaded
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'calculates contention correctly' do
|
52
|
+
lock = limiter.limit(cost: 1.5)
|
53
|
+
expect(lock.contention).to be 1.5
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#utilization' do
|
60
|
+
let(:limiter) { described_class.new(:key, 10) }
|
61
|
+
|
62
|
+
it do
|
63
|
+
expect(limiter.utilization).to be 0.0
|
64
|
+
|
65
|
+
2.times { limiter.limit }
|
66
|
+
expect(limiter.utilization).to be 0.2
|
67
|
+
|
68
|
+
8.times { limiter.limit }
|
69
|
+
expect(limiter.utilization).to be 1.0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#to_s' do
|
74
|
+
let(:limiter) { described_class.new(:key, 3) }
|
75
|
+
|
76
|
+
it { expect(limiter.to_s).to include '3' }
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/spec/test_mode_spec.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
1
|
describe Berater::TestMode do
|
2
|
-
after do
|
3
|
-
Berater.test_mode = nil
|
4
|
-
end
|
5
|
-
|
6
2
|
context 'after test_mode.rb has been loaded' do
|
7
3
|
it 'monkey patches Berater' do
|
8
4
|
expect(Berater).to respond_to(:test_mode)
|
@@ -13,12 +9,12 @@ describe Berater::TestMode do
|
|
13
9
|
end
|
14
10
|
|
15
11
|
it 'prepends Limiter subclasses' do
|
16
|
-
expect(Berater::Unlimiter.ancestors).to include(
|
17
|
-
expect(Berater::Inhibitor.ancestors).to include(
|
12
|
+
expect(Berater::Unlimiter.ancestors).to include(Berater::Limiter::TestMode)
|
13
|
+
expect(Berater::Inhibitor.ancestors).to include(Berater::Limiter::TestMode)
|
18
14
|
end
|
19
15
|
|
20
16
|
it 'preserves the original functionality via super' do
|
21
|
-
expect { Berater::Limiter.new }.to raise_error(
|
17
|
+
expect { Berater::Limiter.new }.to raise_error(NoMethodError)
|
22
18
|
end
|
23
19
|
end
|
24
20
|
|
@@ -55,56 +51,50 @@ describe Berater::TestMode do
|
|
55
51
|
end
|
56
52
|
end
|
57
53
|
|
58
|
-
|
59
|
-
before
|
60
|
-
Berater.redis = nil
|
61
|
-
expect_any_instance_of(Berater::LuaScript).not_to receive(:eval)
|
62
|
-
end
|
63
|
-
|
64
|
-
it_behaves_like 'it is not overloaded'
|
54
|
+
describe '.reset' do
|
55
|
+
before { Berater.test_mode = :pass }
|
65
56
|
|
66
|
-
it '
|
67
|
-
|
57
|
+
it 'resets test_mode' do
|
58
|
+
expect(Berater.test_mode).to be :pass
|
59
|
+
Berater.reset
|
60
|
+
expect(Berater.test_mode).to be nil
|
68
61
|
end
|
69
62
|
end
|
70
63
|
|
71
|
-
shared_examples 'it
|
64
|
+
shared_examples 'it supports test_mode' do
|
72
65
|
before do
|
66
|
+
# without hitting Redis
|
73
67
|
Berater.redis = nil
|
74
68
|
expect_any_instance_of(Berater::LuaScript).not_to receive(:eval)
|
75
69
|
end
|
76
70
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
describe 'Unlimiter' do
|
81
|
-
subject { Berater::Unlimiter.new }
|
71
|
+
context 'with test_mode = :pass' do
|
72
|
+
before { Berater.test_mode = :pass }
|
82
73
|
|
83
|
-
|
84
|
-
before { Berater.test_mode = nil }
|
74
|
+
it_behaves_like 'it is not overloaded'
|
85
75
|
|
86
|
-
it
|
87
|
-
|
76
|
+
it 'always works' do
|
77
|
+
10.times { subject.limit }
|
78
|
+
end
|
88
79
|
end
|
89
80
|
|
90
|
-
context '
|
91
|
-
before { Berater.test_mode = :
|
81
|
+
context 'with test_mode = :fail' do
|
82
|
+
before { Berater.test_mode = :fail }
|
92
83
|
|
93
|
-
it
|
94
|
-
it_behaves_like 'it always works, without redis'
|
84
|
+
it_behaves_like 'it is overloaded'
|
95
85
|
end
|
86
|
+
end
|
96
87
|
|
97
|
-
|
98
|
-
|
88
|
+
describe 'Unlimiter' do
|
89
|
+
subject { Berater::Unlimiter.new }
|
99
90
|
|
100
|
-
|
101
|
-
|
91
|
+
context 'when test_mode = nil' do
|
92
|
+
before { Berater.test_mode = nil }
|
102
93
|
|
103
|
-
|
104
|
-
expect(subject.overloaded?).to be true
|
105
|
-
expect { subject.limit }.to raise_error(Berater::Overloaded)
|
106
|
-
end
|
94
|
+
it_behaves_like 'it is not overloaded'
|
107
95
|
end
|
96
|
+
|
97
|
+
it_behaves_like 'it supports test_mode'
|
108
98
|
end
|
109
99
|
|
110
100
|
describe 'Inhibitor' do
|
@@ -113,47 +103,18 @@ describe Berater::TestMode do
|
|
113
103
|
context 'when test_mode = nil' do
|
114
104
|
before { Berater.test_mode = nil }
|
115
105
|
|
116
|
-
it
|
117
|
-
it_behaves_like 'it never works, without redis'
|
106
|
+
it_behaves_like 'it is overloaded'
|
118
107
|
end
|
119
108
|
|
120
|
-
|
121
|
-
before { Berater.test_mode = :pass }
|
122
|
-
|
123
|
-
it { is_expected.to be_a Berater::Inhibitor }
|
124
|
-
it_behaves_like 'it always works, without redis'
|
125
|
-
end
|
126
|
-
|
127
|
-
context 'when test_mode = :fail' do
|
128
|
-
before { Berater.test_mode = :fail }
|
129
|
-
|
130
|
-
it { is_expected.to be_a Berater::Inhibitor }
|
131
|
-
it_behaves_like 'it never works, without redis'
|
132
|
-
|
133
|
-
it 'supports class specific logic' do
|
134
|
-
expect(subject.inhibited?).to be true
|
135
|
-
expect { subject.limit }.to raise_error(Berater::Inhibitor::Inhibited)
|
136
|
-
end
|
137
|
-
end
|
109
|
+
it_behaves_like 'it supports test_mode'
|
138
110
|
end
|
139
111
|
|
140
112
|
describe 'RateLimiter' do
|
141
113
|
subject { Berater::RateLimiter.new(:key, 1, :second) }
|
142
114
|
|
143
|
-
shared_examples 'a RateLimiter' do
|
144
|
-
it { is_expected.to be_a Berater::RateLimiter }
|
145
|
-
|
146
|
-
it 'checks arguments' do
|
147
|
-
expect {
|
148
|
-
Berater::RateLimiter.new(:key, 1)
|
149
|
-
}.to raise_error(ArgumentError)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
115
|
context 'when test_mode = nil' do
|
154
116
|
before { Berater.test_mode = nil }
|
155
117
|
|
156
|
-
it_behaves_like 'a RateLimiter'
|
157
118
|
it_behaves_like 'it is not overloaded'
|
158
119
|
|
159
120
|
it 'works per usual' do
|
@@ -163,24 +124,7 @@ describe Berater::TestMode do
|
|
163
124
|
end
|
164
125
|
end
|
165
126
|
|
166
|
-
|
167
|
-
before { Berater.test_mode = :pass }
|
168
|
-
|
169
|
-
it_behaves_like 'a RateLimiter'
|
170
|
-
it_behaves_like 'it always works, without redis'
|
171
|
-
end
|
172
|
-
|
173
|
-
context 'when test_mode = :fail' do
|
174
|
-
before { Berater.test_mode = :fail }
|
175
|
-
|
176
|
-
it_behaves_like 'a RateLimiter'
|
177
|
-
it_behaves_like 'it never works, without redis'
|
178
|
-
|
179
|
-
it 'supports class specific logic' do
|
180
|
-
expect(subject.overrated?).to be true
|
181
|
-
expect { subject.limit }.to raise_error(Berater::RateLimiter::Overrated)
|
182
|
-
end
|
183
|
-
end
|
127
|
+
it_behaves_like 'it supports test_mode'
|
184
128
|
end
|
185
129
|
|
186
130
|
describe 'ConcurrencyLimiter' do
|
@@ -189,8 +133,6 @@ describe Berater::TestMode do
|
|
189
133
|
context 'when test_mode = nil' do
|
190
134
|
before { Berater.test_mode = nil }
|
191
135
|
|
192
|
-
it { is_expected.to be_a Berater::ConcurrencyLimiter }
|
193
|
-
|
194
136
|
it_behaves_like 'it is not overloaded'
|
195
137
|
|
196
138
|
it 'works per usual' do
|
@@ -200,26 +142,7 @@ describe Berater::TestMode do
|
|
200
142
|
end
|
201
143
|
end
|
202
144
|
|
203
|
-
|
204
|
-
before { Berater.test_mode = :pass }
|
205
|
-
|
206
|
-
it { is_expected.to be_a Berater::ConcurrencyLimiter }
|
207
|
-
|
208
|
-
it_behaves_like 'it always works, without redis'
|
209
|
-
end
|
210
|
-
|
211
|
-
context 'when test_mode = :fail' do
|
212
|
-
before { Berater.test_mode = :fail }
|
213
|
-
|
214
|
-
it { is_expected.to be_a Berater::ConcurrencyLimiter }
|
215
|
-
|
216
|
-
it_behaves_like 'it never works, without redis'
|
217
|
-
|
218
|
-
it 'supports class specific logic' do
|
219
|
-
expect(subject.incapacitated?).to be true
|
220
|
-
expect { subject.limit }.to raise_error(Berater::ConcurrencyLimiter::Incapacitated)
|
221
|
-
end
|
222
|
-
end
|
145
|
+
it_behaves_like 'it supports test_mode'
|
223
146
|
end
|
224
147
|
|
225
148
|
end
|