berater 0.2.0 → 0.6.1

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,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