berater 0.2.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ describe Berater::Limiter do
2
+ it 'can not be initialized' do
3
+ expect { described_class.new }.to raise_error(NotImplementedError)
4
+ end
5
+
6
+ describe 'abstract methods' do
7
+ let(:limiter) { Class.new(described_class).new(:key, 1) }
8
+
9
+ it do
10
+ expect { limiter.limit }.to raise_error(NotImplementedError)
11
+ expect { limiter.overloaded? }.to raise_error(NotImplementedError)
12
+ end
13
+ end
14
+
15
+ describe '==' do
16
+ let(:limiter) { Berater::RateLimiter.new(:key, 1, :second) }
17
+
18
+ it 'equals itself' do
19
+ expect(limiter).to eq limiter
20
+ end
21
+
22
+ it 'equals something with the same initialization parameters' do
23
+ expect(limiter).to eq(
24
+ Berater::RateLimiter.new(:key, 1, :second)
25
+ )
26
+ end
27
+
28
+ it 'equals something with equvalent initialization parameters' do
29
+ expect(limiter).to eq(
30
+ Berater::RateLimiter.new(:key, 1, 1)
31
+ )
32
+ end
33
+
34
+ it 'does not equal something different' do
35
+ expect(limiter).not_to eq(
36
+ Berater::RateLimiter.new(:key, 2, :second)
37
+ )
38
+
39
+ expect(limiter).not_to eq(
40
+ Berater::RateLimiter.new(:keyz, 1, :second)
41
+ )
42
+
43
+ expect(limiter).not_to eq(
44
+ Berater::RateLimiter.new(:key, 1, :minute)
45
+ )
46
+ end
47
+
48
+ it 'does not equal something altogether different' do
49
+ expect(limiter).not_to eq(
50
+ Berater::ConcurrencyLimiter.new(:key, 1)
51
+ )
52
+ end
53
+
54
+ it 'works for ConcurrencyLimiter too' do
55
+ limiter = Berater::ConcurrencyLimiter.new(:key, 1)
56
+ expect(limiter).to eq limiter
57
+
58
+ expect(limiter).not_to eq(
59
+ Berater::ConcurrencyLimiter.new(:key, 1, timeout: 1)
60
+ )
61
+ end
62
+
63
+ it 'and the others' do
64
+ unlimiter = Berater::Unlimiter.new
65
+ expect(unlimiter).to eq unlimiter
66
+
67
+ expect(unlimiter).not_to eq Berater::Inhibitor.new
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,97 @@
1
+ describe Berater::LuaScript do
2
+ subject { Berater::LuaScript('return redis.call("PING")') }
3
+
4
+ before { redis.script(:flush) }
5
+
6
+ let(:redis) { Berater.redis }
7
+
8
+ it { is_expected.to be_a Berater::LuaScript }
9
+
10
+ describe '#eval' do
11
+ def ping
12
+ expect(subject.eval(redis)).to eq 'PONG'
13
+ end
14
+
15
+ it { ping }
16
+
17
+ it 'loads the script into redis' do
18
+ expect(redis).to receive(:evalsha).once.and_call_original
19
+ expect(redis).to receive(:eval).once.and_call_original
20
+ ping
21
+ expect(subject.loaded?(redis)).to be true
22
+ end
23
+ end
24
+
25
+ describe '#load' do
26
+ it 'loads script into redis' do
27
+ expect(redis.script(:exists, subject.sha)).to be false
28
+ subject.load(redis)
29
+ expect(redis.script(:exists, subject.sha)).to be true
30
+ end
31
+
32
+ it 'returns the sha' do
33
+ expect(subject.load(redis)).to eq subject.sha
34
+ end
35
+
36
+ it 'validates the returned sha' do
37
+ allow(redis).to receive(:script).with(:flush).and_call_original
38
+ expect(redis).to receive(:script).with(:load, String).and_return('abc')
39
+ expect { subject.load(redis) }.to raise_error(RuntimeError)
40
+ end
41
+ end
42
+
43
+ describe '#loaded?' do
44
+ it do
45
+ expect(subject.loaded?(redis)).to be false
46
+ subject.load(redis)
47
+ expect(subject.loaded?(redis)).to be true
48
+ end
49
+ end
50
+
51
+ describe '#to_s' do
52
+ it { expect(subject.to_s).to be subject.source }
53
+ end
54
+
55
+ describe '#minify' do
56
+ subject do
57
+ expect(
58
+ Berater::LuaScript(lua).send(:minify)
59
+ ).to eq expected
60
+ end
61
+
62
+ context do
63
+ let(:lua) do <<-LUA
64
+ -- this comment gets removed
65
+ redis.call('PING') -- this one too
66
+ LUA
67
+ end
68
+
69
+ let(:expected) { "redis.call('PING')" }
70
+
71
+ it { subject }
72
+ end
73
+
74
+ context 'with if statement' do
75
+ let(:lua) do <<~LUA
76
+ if condition then
77
+ call
78
+ end
79
+
80
+ return 123
81
+ LUA
82
+ end
83
+
84
+ let(:expected) do
85
+ [
86
+ 'if condition then',
87
+ 'call',
88
+ 'end',
89
+ 'return 123'
90
+ ].join "\n"
91
+ end
92
+
93
+ it { subject }
94
+ end
95
+ end
96
+
97
+ end
@@ -1,4 +1,5 @@
1
- describe 'be_overloaded' do
1
+ describe Berater::Matchers::Overloaded do
2
+
2
3
  context 'Berater::Unlimiter' do
3
4
  let(:limiter) { Berater.new(:key, :unlimited) }
4
5
 
@@ -32,7 +33,7 @@ describe 'be_overloaded' do
32
33
  end
33
34
 
34
35
  context 'Berater::RateLimiter' do
35
- let(:limiter) { Berater.new(:key, :rate, 1, :second) }
36
+ let(:limiter) { Berater.new(:key, 1, :second) }
36
37
 
37
38
  it { expect(limiter).not_to be_overloaded }
38
39
  it { expect(limiter).not_to be_inhibited }
@@ -67,7 +68,7 @@ describe 'be_overloaded' do
67
68
  end
68
69
 
69
70
  context 'Berater::ConcurrencyLimiter' do
70
- let(:limiter) { Berater.new(:key, :concurrency, 1) }
71
+ let(:limiter) { Berater.new(:key, 1) }
71
72
 
72
73
  it { expect(limiter).not_to be_overloaded }
73
74
  it { expect(limiter).not_to be_inhibited }
@@ -115,4 +116,71 @@ describe 'be_overloaded' do
115
116
  end
116
117
  end
117
118
  end
119
+
120
+ context 'when matchers fail' do
121
+ let(:unlimiter) { Berater::Unlimiter.new }
122
+ let(:inhibitor) { Berater::Inhibitor.new }
123
+
124
+ it 'catches false negatives' do
125
+ expect {
126
+ expect(unlimiter).to be_overloaded
127
+ }.to fail_including('expected to be overloaded')
128
+
129
+ expect {
130
+ expect { unlimiter }.to be_overloaded
131
+ }.to fail_including('expected to be overloaded')
132
+
133
+ expect {
134
+ expect { unlimiter.limit }.to be_overloaded
135
+ }.to fail_including("expected #{Berater::Overloaded} to be raised")
136
+
137
+ expect {
138
+ expect { 123 }.to be_overloaded
139
+ }.to fail_including("expected #{Berater::Overloaded} to be raised")
140
+ end
141
+
142
+ it 'catches false positives' do
143
+ expect {
144
+ expect(inhibitor).not_to be_overloaded
145
+ }.to fail_including('expected not to be overloaded')
146
+
147
+ expect {
148
+ expect { inhibitor }.not_to be_overloaded
149
+ }.to fail_including('expected not to be overloaded')
150
+
151
+ expect {
152
+ expect { inhibitor.limit }.not_to be_overloaded
153
+ }.to fail_including("did not expect #{Berater::Overloaded} to be raised")
154
+
155
+ expect {
156
+ expect { raise Berater::Overloaded }.not_to be_overloaded
157
+ }.to fail_including("did not expect #{Berater::Overloaded} to be raised")
158
+ end
159
+
160
+ it 'supports different verbs' do
161
+ expect {
162
+ expect { unlimiter }.to be_overrated
163
+ }.to fail_including('expected to be overrated')
164
+
165
+ expect {
166
+ expect { unlimiter }.to be_incapacitated
167
+ }.to fail_including('expected to be incapacitated')
168
+ end
169
+
170
+ it 'supports different exceptions' do
171
+ expect {
172
+ expect { 123 }.to be_overrated
173
+ }.to fail_including(
174
+ "expected #{Berater::RateLimiter::Overrated} to be raised"
175
+ )
176
+
177
+ expect {
178
+ expect {
179
+ raise Berater::ConcurrencyLimiter::Incapacitated
180
+ }.not_to be_incapacitated
181
+ }.to fail_including(
182
+ "did not expect #{Berater::ConcurrencyLimiter::Incapacitated} to be raised"
183
+ )
184
+ end
185
+ end
118
186
  end
@@ -1,12 +1,13 @@
1
1
  describe Berater::RateLimiter do
2
+ it_behaves_like 'a limiter', Berater.new(:key, 3, :second)
2
3
 
3
4
  describe '.new' do
4
5
  let(:limiter) { described_class.new(:key, 1, :second) }
5
6
 
6
7
  it 'initializes' do
7
8
  expect(limiter.key).to be :key
8
- expect(limiter.count).to eq 1
9
- expect(limiter.interval).to eq 1
9
+ expect(limiter.capacity).to eq 1
10
+ expect(limiter.interval).to eq :second
10
11
  end
11
12
 
12
13
  it 'has default values' do
@@ -14,71 +15,48 @@ describe Berater::RateLimiter do
14
15
  end
15
16
  end
16
17
 
17
- describe '#count' do
18
- def expect_count(count)
19
- limiter = described_class.new(:key, count, :second)
20
- expect(limiter.count).to eq count
18
+ describe '#capacity' do
19
+ def expect_capacity(capacity)
20
+ limiter = described_class.new(:key, capacity, :second)
21
+ expect(limiter.capacity).to eq capacity
21
22
  end
22
23
 
23
- it { expect_count(0) }
24
- it { expect_count(1) }
25
- it { expect_count(100) }
24
+ it { expect_capacity(0) }
25
+ it { expect_capacity(1) }
26
+ it { expect_capacity(1.5) }
27
+ it { expect_capacity(100) }
26
28
 
27
29
  context 'with erroneous values' do
28
- def expect_bad_count(count)
30
+ def expect_bad_capacity(capacity)
29
31
  expect do
30
- described_class.new(:key, count, :second)
32
+ described_class.new(:key, capacity, :second)
31
33
  end.to raise_error ArgumentError
32
34
  end
33
35
 
34
- it { expect_bad_count(0.5) }
35
- it { expect_bad_count(-1) }
36
- it { expect_bad_count('1') }
37
- it { expect_bad_count(:one) }
36
+ it { expect_bad_capacity(-1) }
37
+ it { expect_bad_capacity('1') }
38
+ it { expect_bad_capacity(:one) }
38
39
  end
39
40
  end
40
41
 
41
42
  describe '#interval' do
42
- def expect_interval(interval, expected)
43
- limiter = described_class.new(:key, 1, interval)
44
- expect(limiter.interval).to eq expected
45
- end
46
-
47
- context 'with ints' do
48
- it { expect_interval(0, 0) }
49
- it { expect_interval(1, 1) }
50
- it { expect_interval(33, 33) }
51
- end
52
-
53
- context 'with symbols' do
54
- it { expect_interval(:sec, 1) }
55
- it { expect_interval(:second, 1) }
56
- it { expect_interval(:seconds, 1) }
43
+ # see spec/utils_spec.rb for more
57
44
 
58
- it { expect_interval(:min, 60) }
59
- it { expect_interval(:minute, 60) }
60
- it { expect_interval(:minutes, 60) }
45
+ subject { described_class.new(:key, 1, :second) }
61
46
 
62
- it { expect_interval(:hour, 3600) }
63
- it { expect_interval(:hours, 3600) }
47
+ it 'saves the interval in original and millisecond format' do
48
+ expect(subject.interval).to be :second
49
+ expect(subject.instance_variable_get(:@interval_msec)).to be 10**3
64
50
  end
65
51
 
66
- context 'with strings' do
67
- it { expect_interval('sec', 1) }
68
- it { expect_interval('minute', 60) }
69
- it { expect_interval('hours', 3600) }
70
- end
71
-
72
- context 'with erroneous values' do
73
- def expect_bad_interval(interval)
74
- expect do
75
- described_class.new(:key, 1, interval)
76
- end.to raise_error(ArgumentError)
77
- end
52
+ it 'must be > 0' do
53
+ expect {
54
+ described_class.new(:key, 1, 0)
55
+ }.to raise_error(ArgumentError)
78
56
 
79
- it { expect_bad_interval(-1) }
80
- it { expect_bad_interval(:secondz) }
81
- it { expect_bad_interval('huor') }
57
+ expect {
58
+ described_class.new(:key, 1, -1)
59
+ }.to raise_error(ArgumentError)
82
60
  end
83
61
  end
84
62
 
@@ -100,43 +78,151 @@ describe Berater::RateLimiter do
100
78
  expect(limiter).to be_overrated
101
79
  end
102
80
 
103
- it 'limit resets over time' do
81
+ it 'resets limit over time' do
104
82
  3.times { limiter.limit }
105
83
  expect(limiter).to be_overrated
106
84
 
107
- # travel forward a second
108
85
  Timecop.freeze(1)
109
86
 
110
87
  3.times { limiter.limit }
111
88
  expect(limiter).to be_overrated
112
89
  end
113
- end
114
90
 
115
- context 'with same key, different limiters' do
116
- let(:limiter_one) { described_class.new(:key, 1, :second) }
117
- let(:limiter_two) { described_class.new(:key, 1, :second) }
91
+ context 'with millisecond precision' do
92
+ it 'resets limit over time' do
93
+ 3.times { limiter.limit }
94
+ expect(limiter).to be_overrated
95
+
96
+ # travel forward to just before the count decrements
97
+ Timecop.freeze(0.333)
98
+ expect(limiter).to be_overrated
99
+
100
+ # traveling one more millisecond will decrement the count
101
+ Timecop.freeze(0.001)
102
+ limiter.limit
103
+ expect(limiter).to be_overrated
104
+ end
105
+
106
+ it 'works when drip rate is < 1 per millisecond' do
107
+ limiter = described_class.new(:key, 2_000, :second)
118
108
 
119
- it 'works as expected' do
120
- expect(limiter_one.limit).not_to be_overrated
109
+ limiter.capacity.times { limiter.limit }
110
+ expect(limiter).to be_overrated
121
111
 
122
- expect(limiter_one).to be_overrated
123
- expect(limiter_two).to be_overrated
112
+ Timecop.freeze(0.001)
113
+ expect(limiter).not_to be_overrated
114
+
115
+ 2.times { limiter.limit }
116
+ end
117
+ end
118
+
119
+ context 'when capacity is a Float' do
120
+ let(:limiter) { described_class.new(:key, 1.5, :second) }
121
+
122
+ it 'still works' do
123
+ limiter.limit
124
+ expect(limiter).not_to be_overrated
125
+
126
+ expect { limiter.limit }.to be_overrated
127
+
128
+ limiter.limit(cost: 0.5)
129
+ end
130
+ end
131
+
132
+ it 'accepts a dynamic capacity' do
133
+ limiter = described_class.new(:key, 1, :second)
134
+
135
+ expect { limiter.limit(capacity: 0) }.to be_overrated
136
+ 5.times { limiter.limit(capacity: 10) }
137
+ expect { limiter }.to be_overrated
138
+ end
139
+
140
+ context 'works with cost parameter' do
141
+ it { expect { limiter.limit(cost: 4) }.to be_overrated }
142
+
143
+ it 'works within limit' do
144
+ limiter.limit(cost: 3)
145
+ expect { limiter.limit }.to be_overrated
146
+ end
147
+
148
+ it 'resets over time' do
149
+ limiter.limit(cost: 3)
150
+ expect(limiter).to be_overrated
151
+
152
+ Timecop.freeze(1)
153
+ expect(limiter).not_to be_overrated
154
+ end
155
+
156
+ it 'can be a Float' do
157
+ 2.times { limiter.limit(cost: 1.5) }
158
+ expect(limiter).to be_overrated
159
+ end
160
+ end
161
+
162
+ context 'with same key, different limiters' do
163
+ let(:limiter_one) { described_class.new(:key, 1, :second) }
164
+ let(:limiter_two) { described_class.new(:key, 1, :second) }
165
+
166
+ it 'works as expected' do
167
+ expect(limiter_one.limit).not_to be_overrated
168
+
169
+ expect(limiter_one).to be_overrated
170
+ expect(limiter_two).to be_overrated
171
+ end
172
+ end
173
+
174
+ context 'with different keys, different limiters' do
175
+ let(:limiter_one) { described_class.new(:one, 1, :second) }
176
+ let(:limiter_two) { described_class.new(:two, 2, :second) }
177
+
178
+ it 'works as expected' do
179
+ expect(limiter_one.limit).not_to be_overrated
180
+ expect(limiter_two.limit).not_to be_overrated
181
+
182
+ expect(limiter_one).to be_overrated
183
+ expect(limiter_two.limit).not_to be_overrated
184
+
185
+ expect(limiter_one).to be_overrated
186
+ expect(limiter_two).to be_overrated
187
+ end
124
188
  end
125
189
  end
126
190
 
127
- context 'with different keys, different limiters' do
128
- let(:limiter_one) { described_class.new(:one, 1, :second) }
129
- let(:limiter_two) { described_class.new(:two, 2, :second) }
191
+ describe '#overloaded?' do
192
+ let(:limiter) { described_class.new(:key, 1, :second) }
130
193
 
131
- it 'works as expected' do
132
- expect(limiter_one.limit).not_to be_overrated
133
- expect(limiter_two.limit).not_to be_overrated
194
+ it 'works' do
195
+ expect(limiter.overloaded?).to be false
196
+ limiter.limit
197
+ expect(limiter.overloaded?).to be true
198
+ Timecop.freeze(1)
199
+ expect(limiter.overloaded?).to be false
200
+ end
201
+ end
202
+
203
+ describe '#to_s' do
204
+ def check(capacity, interval, expected)
205
+ expect(
206
+ described_class.new(:key, capacity, interval).to_s
207
+ ).to match(expected)
208
+ end
134
209
 
135
- expect(limiter_one).to be_overrated
136
- expect(limiter_two.limit).not_to be_overrated
210
+ it 'works with symbols' do
211
+ check(1, :second, /1 per second/)
212
+ check(1, :minute, /1 per minute/)
213
+ check(1, :hour, /1 per hour/)
214
+ end
215
+
216
+ it 'works with strings' do
217
+ check(1, 'second', /1 per second/)
218
+ check(1, 'minute', /1 per minute/)
219
+ check(1, 'hour', /1 per hour/)
220
+ end
137
221
 
138
- expect(limiter_one).to be_overrated
139
- expect(limiter_two).to be_overrated
222
+ it 'works with integers' do
223
+ check(1, 1, /1 every second/)
224
+ check(1, 2, /1 every 2 seconds/)
225
+ check(2, 3, /2 every 3 seconds/)
140
226
  end
141
227
  end
142
228