berater 0.1.3 → 0.5.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 +33 -35
- data/lib/berater/concurrency_limiter.rb +63 -110
- data/lib/berater/dsl.rb +68 -0
- data/lib/berater/inhibitor.rb +9 -10
- data/lib/berater/limiter.rb +76 -0
- data/lib/berater/lock.rb +27 -0
- data/lib/berater/lua_script.rb +55 -0
- data/lib/berater/rate_limiter.rb +74 -55
- data/lib/berater/rspec.rb +14 -0
- data/lib/berater/rspec/matchers.rb +62 -0
- data/lib/berater/test_mode.rb +52 -0
- data/lib/berater/unlimiter.rb +7 -9
- data/lib/berater/utils.rb +46 -0
- data/lib/berater/version.rb +1 -1
- data/spec/berater_spec.rb +43 -101
- data/spec/concurrency_limiter_spec.rb +163 -99
- data/spec/dsl_refinement_spec.rb +46 -0
- data/spec/dsl_spec.rb +72 -0
- data/spec/inhibitor_spec.rb +4 -18
- data/spec/limiter_spec.rb +71 -0
- data/spec/lua_script_spec.rb +97 -0
- data/spec/{matcher_spec.rb → matchers_spec.rb} +16 -4
- data/spec/rate_limiter_spec.rb +124 -102
- data/spec/riddle_spec.rb +102 -0
- data/spec/test_mode_spec.rb +213 -0
- data/spec/unlimiter_spec.rb +6 -37
- data/spec/utils_spec.rb +78 -0
- metadata +41 -8
- data/lib/berater/base_limiter.rb +0 -32
- data/spec/concurrency_lock_spec.rb +0 -50
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'berater/dsl'
|
2
|
+
|
3
|
+
describe Berater do
|
4
|
+
using Berater::DSL
|
5
|
+
|
6
|
+
it 'instatiates an Unlimiter' do
|
7
|
+
limiter = Berater.new(:key) { unlimited }
|
8
|
+
expect(limiter).to be_a Berater::Unlimiter
|
9
|
+
expect(limiter.key).to be :key
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'instatiates an Inhibiter' do
|
13
|
+
limiter = Berater.new(:key) { inhibited }
|
14
|
+
expect(limiter).to be_a Berater::Inhibitor
|
15
|
+
expect(limiter.key).to be :key
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'instatiates a RateLimiter' do
|
19
|
+
limiter = Berater.new(:key) { 1.per second }
|
20
|
+
expect(limiter).to be_a Berater::RateLimiter
|
21
|
+
expect(limiter.key).to be :key
|
22
|
+
expect(limiter.capacity).to be 1
|
23
|
+
expect(limiter.interval).to be :second
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'instatiates a ConcurrencyLimiter' do
|
27
|
+
limiter = Berater.new(:key, timeout: 2) { 1.at_once }
|
28
|
+
expect(limiter).to be_a Berater::ConcurrencyLimiter
|
29
|
+
expect(limiter.key).to be :key
|
30
|
+
expect(limiter.capacity).to be 1
|
31
|
+
expect(limiter.timeout).to be 2
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not accept args and dsl block' do
|
35
|
+
expect {
|
36
|
+
Berater.new(:key, 2) { 3.at_once }
|
37
|
+
}.to raise_error(ArgumentError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'requires either mode or dsl block' do
|
41
|
+
expect {
|
42
|
+
Berater.new(:key)
|
43
|
+
}.to raise_error(ArgumentError)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'berater/dsl'
|
2
|
+
|
3
|
+
describe Berater::DSL do
|
4
|
+
def check(expected, &block)
|
5
|
+
expect(Berater::DSL.eval(&block)).to eq expected
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'rate mode' do
|
9
|
+
it 'has keywords' do
|
10
|
+
check(:second) { second }
|
11
|
+
check(:minute) { minute }
|
12
|
+
check(:hour) { hour }
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'parses' do
|
16
|
+
check([ 1, :second ]) { 1.per second }
|
17
|
+
check([ 3, :minute ]) { 3.per minute }
|
18
|
+
check([ 5, :hour ]) { 5.every hour }
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'cleans up afterward' do
|
22
|
+
check([ 1, :second ]) { 1.per second }
|
23
|
+
|
24
|
+
expect(Integer).not_to respond_to(:per)
|
25
|
+
expect(Integer).not_to respond_to(:every)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'works with variables' do
|
29
|
+
count = 1
|
30
|
+
interval = :second
|
31
|
+
|
32
|
+
check([ count, interval ]) { count.per interval }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'concurrency mode' do
|
37
|
+
it 'parses' do
|
38
|
+
check([ 1 ]) { 1.at_once }
|
39
|
+
check([ 3 ]) { 3.at_a_time }
|
40
|
+
check([ 5 ]) { 5.concurrently }
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'cleans up afterward' do
|
44
|
+
check([ 1 ]) { 1.at_once }
|
45
|
+
|
46
|
+
expect(Integer).not_to respond_to(:at_once)
|
47
|
+
expect(Integer).not_to respond_to(:at_a_time)
|
48
|
+
expect(Integer).not_to respond_to(:concurrently)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'works with constants' do
|
52
|
+
class Foo
|
53
|
+
CAPACITY = 3
|
54
|
+
end
|
55
|
+
|
56
|
+
check([ Foo::CAPACITY ]) { Foo::CAPACITY.at_once }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'unlimited mode' do
|
61
|
+
it 'has keywords' do
|
62
|
+
check(:unlimited) { unlimited }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'inhibited mode' do
|
67
|
+
it 'has keywords' do
|
68
|
+
check(:inhibited) { inhibited }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/spec/inhibitor_spec.rb
CHANGED
@@ -1,37 +1,23 @@
|
|
1
1
|
describe Berater::Inhibitor do
|
2
|
-
before { Berater.mode = :inhibited }
|
3
|
-
|
4
2
|
describe '.new' do
|
5
3
|
it 'initializes without any arguments or options' do
|
6
4
|
expect(described_class.new).to be_a described_class
|
7
5
|
end
|
8
6
|
|
9
7
|
it 'initializes with any arguments and options' do
|
10
|
-
expect(described_class.new(:abc, x: 123)).to be_a described_class
|
8
|
+
expect(described_class.new(:abc, :def, x: 123)).to be_a described_class
|
11
9
|
end
|
12
10
|
|
13
11
|
it 'has default values' do
|
14
|
-
expect(described_class.new.key).to
|
12
|
+
expect(described_class.new.key).to be :inhibitor
|
15
13
|
expect(described_class.new.redis).to be Berater.redis
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
|
-
describe '.limit' do
|
20
|
-
it 'always limits' do
|
21
|
-
expect { described_class.limit }.to be_inhibited
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'works with any arguments or options' do
|
25
|
-
expect { described_class.limit(:abc, x: 123) }.to be_inhibited
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
17
|
describe '#limit' do
|
30
|
-
|
18
|
+
subject { described_class.new }
|
31
19
|
|
32
|
-
|
33
|
-
expect { described_class.limit }.to be_inhibited
|
34
|
-
end
|
20
|
+
it_behaves_like 'it is overloaded'
|
35
21
|
end
|
36
22
|
|
37
23
|
end
|
@@ -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,6 +1,6 @@
|
|
1
1
|
describe 'be_overloaded' do
|
2
2
|
context 'Berater::Unlimiter' do
|
3
|
-
let(:limiter) { Berater.new(:unlimited) }
|
3
|
+
let(:limiter) { Berater.new(:key, :unlimited) }
|
4
4
|
|
5
5
|
it { expect(limiter).not_to be_overloaded }
|
6
6
|
it { expect(limiter).not_to be_inhibited }
|
@@ -16,10 +16,16 @@ describe 'be_overloaded' do
|
|
16
16
|
it { expect { limiter.limit }.not_to be_inhibited }
|
17
17
|
it { expect { limiter.limit }.not_to be_overrated }
|
18
18
|
it { expect { limiter.limit }.not_to be_incapacitated }
|
19
|
+
|
20
|
+
it 'catches false positives' do
|
21
|
+
expect {
|
22
|
+
expect { limiter }.to be_overloaded
|
23
|
+
}.to fail
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
27
|
context 'Berater::Inhibitor' do
|
22
|
-
let(:limiter) { Berater.new(:inhibited) }
|
28
|
+
let(:limiter) { Berater.new(:key, :inhibited) }
|
23
29
|
|
24
30
|
it { expect(limiter).to be_overloaded }
|
25
31
|
it { expect(limiter).to be_inhibited }
|
@@ -29,10 +35,16 @@ describe 'be_overloaded' do
|
|
29
35
|
|
30
36
|
it { expect { limiter.limit }.to be_overloaded }
|
31
37
|
it { expect { limiter.limit }.to be_inhibited }
|
38
|
+
|
39
|
+
it 'catches false negatives' do
|
40
|
+
expect {
|
41
|
+
expect { limiter }.not_to be_overloaded
|
42
|
+
}.to fail
|
43
|
+
end
|
32
44
|
end
|
33
45
|
|
34
46
|
context 'Berater::RateLimiter' do
|
35
|
-
let(:limiter) { Berater.new(:
|
47
|
+
let(:limiter) { Berater.new(:key, 1, :second) }
|
36
48
|
|
37
49
|
it { expect(limiter).not_to be_overloaded }
|
38
50
|
it { expect(limiter).not_to be_inhibited }
|
@@ -67,7 +79,7 @@ describe 'be_overloaded' do
|
|
67
79
|
end
|
68
80
|
|
69
81
|
context 'Berater::ConcurrencyLimiter' do
|
70
|
-
let(:limiter) { Berater.new(:
|
82
|
+
let(:limiter) { Berater.new(:key, 1) }
|
71
83
|
|
72
84
|
it { expect(limiter).not_to be_overloaded }
|
73
85
|
it { expect(limiter).not_to be_inhibited }
|
data/spec/rate_limiter_spec.rb
CHANGED
@@ -1,165 +1,187 @@
|
|
1
1
|
describe Berater::RateLimiter do
|
2
|
-
|
2
|
+
it_behaves_like 'a limiter', Berater.new(:key, 3, :second)
|
3
3
|
|
4
4
|
describe '.new' do
|
5
|
-
let(:limiter) { described_class.new(1, :second) }
|
5
|
+
let(:limiter) { described_class.new(:key, 1, :second) }
|
6
6
|
|
7
7
|
it 'initializes' do
|
8
|
-
expect(limiter.
|
9
|
-
expect(limiter.
|
8
|
+
expect(limiter.key).to be :key
|
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
|
13
|
-
expect(limiter.key).to eq described_class.to_s
|
14
14
|
expect(limiter.redis).to be Berater.redis
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
describe '#
|
19
|
-
def
|
20
|
-
limiter = described_class.new(
|
21
|
-
expect(limiter.
|
18
|
+
describe '#capacity' do
|
19
|
+
def expect_capacity(capacity)
|
20
|
+
limiter = described_class.new(:key, capacity, :second)
|
21
|
+
expect(limiter.capacity).to eq capacity
|
22
22
|
end
|
23
23
|
|
24
|
-
it {
|
25
|
-
it {
|
26
|
-
it {
|
24
|
+
it { expect_capacity(0) }
|
25
|
+
it { expect_capacity(1) }
|
26
|
+
it { expect_capacity(100) }
|
27
27
|
|
28
28
|
context 'with erroneous values' do
|
29
|
-
def
|
29
|
+
def expect_bad_capacity(capacity)
|
30
30
|
expect do
|
31
|
-
described_class.new(
|
31
|
+
described_class.new(:key, capacity, :second)
|
32
32
|
end.to raise_error ArgumentError
|
33
33
|
end
|
34
34
|
|
35
|
-
it {
|
36
|
-
it {
|
37
|
-
it {
|
38
|
-
it {
|
35
|
+
it { expect_bad_capacity(0.5) }
|
36
|
+
it { expect_bad_capacity(-1) }
|
37
|
+
it { expect_bad_capacity('1') }
|
38
|
+
it { expect_bad_capacity(:one) }
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
describe '#interval' do
|
43
|
-
|
44
|
-
limiter = described_class.new(1, interval)
|
45
|
-
expect(limiter.interval).to eq expected
|
46
|
-
end
|
47
|
-
|
48
|
-
context 'with ints' do
|
49
|
-
it { expect_interval(0, 0) }
|
50
|
-
it { expect_interval(1, 1) }
|
51
|
-
it { expect_interval(33, 33) }
|
52
|
-
end
|
53
|
-
|
54
|
-
context 'with symbols' do
|
55
|
-
it { expect_interval(:sec, 1) }
|
56
|
-
it { expect_interval(:second, 1) }
|
57
|
-
it { expect_interval(:seconds, 1) }
|
43
|
+
# see spec/utils_spec.rb for more
|
58
44
|
|
59
|
-
|
60
|
-
it { expect_interval(:minute, 60) }
|
61
|
-
it { expect_interval(:minutes, 60) }
|
45
|
+
subject { described_class.new(:key, 1, :second) }
|
62
46
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
context 'with strings' do
|
68
|
-
it { expect_interval('sec', 1) }
|
69
|
-
it { expect_interval('minute', 60) }
|
70
|
-
it { expect_interval('hours', 3600) }
|
71
|
-
end
|
72
|
-
|
73
|
-
context 'with erroneous values' do
|
74
|
-
def expect_bad_interval(interval)
|
75
|
-
expect do
|
76
|
-
described_class.new(1, interval)
|
77
|
-
end.to raise_error(ArgumentError)
|
78
|
-
end
|
79
|
-
|
80
|
-
it { expect_bad_interval(-1) }
|
81
|
-
it { expect_bad_interval(:secondz) }
|
82
|
-
it { expect_bad_interval('huor') }
|
47
|
+
it 'saves the interval in original and microsecond format' do
|
48
|
+
expect(subject.interval).to be :second
|
49
|
+
expect(subject.instance_variable_get(:@interval_usec)).to be 10**6
|
83
50
|
end
|
84
51
|
end
|
85
52
|
|
86
53
|
describe '#limit' do
|
87
|
-
let(:limiter) { described_class.new(3, :second) }
|
54
|
+
let(:limiter) { described_class.new(:key, 3, :second) }
|
88
55
|
|
89
56
|
it 'works' do
|
90
|
-
expect(limiter.limit).to eq 1
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'counts' do
|
94
|
-
expect(limiter.limit).to eq 1
|
95
|
-
expect(limiter.limit).to eq 2
|
96
|
-
expect(limiter.limit).to eq 3
|
97
|
-
end
|
98
|
-
|
99
|
-
it 'yields' do
|
100
57
|
expect {|b| limiter.limit(&b) }.to yield_control
|
101
58
|
expect(limiter.limit { 123 }).to eq 123
|
102
59
|
end
|
103
60
|
|
61
|
+
it 'works without a block' do
|
62
|
+
expect(limiter.limit).to be_a Berater::Lock
|
63
|
+
end
|
64
|
+
|
104
65
|
it 'limits excessive calls' do
|
105
66
|
3.times { limiter.limit }
|
106
67
|
|
107
|
-
expect
|
68
|
+
expect(limiter).to be_overrated
|
108
69
|
end
|
109
70
|
|
110
|
-
it 'limit resets over time' do
|
111
|
-
|
112
|
-
expect(limiter.limit).to eq 2
|
113
|
-
expect(limiter.limit).to eq 3
|
71
|
+
it 'limit resets over time, with millisecond precision' do
|
72
|
+
3.times { limiter.limit }
|
114
73
|
expect(limiter).to be_overrated
|
115
74
|
|
116
|
-
# travel forward
|
75
|
+
# travel forward to just before the count decrements
|
76
|
+
Timecop.freeze(0.333)
|
77
|
+
expect(limiter).to be_overrated
|
78
|
+
|
79
|
+
# traveling one more millisecond will decrement the count
|
80
|
+
Timecop.freeze(0.001)
|
81
|
+
limiter.limit
|
82
|
+
expect(limiter).to be_overrated
|
83
|
+
|
84
|
+
# traveling 1 second will reset the count
|
117
85
|
Timecop.freeze(1)
|
118
86
|
|
119
|
-
|
120
|
-
expect(limiter.limit).to eq 2
|
121
|
-
expect(limiter.limit).to eq 3
|
87
|
+
3.times { limiter.limit }
|
122
88
|
expect(limiter).to be_overrated
|
123
89
|
end
|
124
|
-
end
|
125
90
|
|
126
|
-
|
127
|
-
|
128
|
-
let(:limiter_two) { described_class.new(1, :second) }
|
91
|
+
it 'accepts a dynamic capacity' do
|
92
|
+
limiter = described_class.new(:key, 1, :second)
|
129
93
|
|
130
|
-
|
131
|
-
|
94
|
+
expect { limiter.limit(capacity: 0) }.to be_overrated
|
95
|
+
5.times { limiter.limit(capacity: 10) }
|
96
|
+
expect { limiter }.to be_overrated
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'works with cost parameter' do
|
100
|
+
it { expect { limiter.limit(cost: 4) }.to be_overrated }
|
101
|
+
|
102
|
+
it 'works within limit' do
|
103
|
+
limiter.limit(cost: 3)
|
104
|
+
expect { limiter.limit }.to be_overrated
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'resets over time' do
|
108
|
+
limiter.limit(cost: 3)
|
109
|
+
expect(limiter).to be_overrated
|
110
|
+
|
111
|
+
Timecop.freeze(1)
|
112
|
+
expect(limiter).not_to be_overrated
|
113
|
+
end
|
132
114
|
|
133
|
-
|
134
|
-
|
115
|
+
it 'can be a Float' do
|
116
|
+
2.times { limiter.limit(cost: 1.5) }
|
117
|
+
expect(limiter).to be_overrated
|
118
|
+
end
|
135
119
|
end
|
136
|
-
end
|
137
120
|
|
138
|
-
|
139
|
-
|
121
|
+
context 'with same key, different limiters' do
|
122
|
+
let(:limiter_one) { described_class.new(:key, 1, :second) }
|
123
|
+
let(:limiter_two) { described_class.new(:key, 1, :second) }
|
140
124
|
|
141
|
-
|
142
|
-
|
143
|
-
expect { limiter.limit(key: :one) }.to be_overrated
|
125
|
+
it 'works as expected' do
|
126
|
+
expect(limiter_one.limit).not_to be_overrated
|
144
127
|
|
145
|
-
|
146
|
-
|
128
|
+
expect(limiter_one).to be_overrated
|
129
|
+
expect(limiter_two).to be_overrated
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'with different keys, different limiters' do
|
134
|
+
let(:limiter_one) { described_class.new(:one, 1, :second) }
|
135
|
+
let(:limiter_two) { described_class.new(:two, 2, :second) }
|
136
|
+
|
137
|
+
it 'works as expected' do
|
138
|
+
expect(limiter_one.limit).not_to be_overrated
|
139
|
+
expect(limiter_two.limit).not_to be_overrated
|
140
|
+
|
141
|
+
expect(limiter_one).to be_overrated
|
142
|
+
expect(limiter_two.limit).not_to be_overrated
|
143
|
+
|
144
|
+
expect(limiter_one).to be_overrated
|
145
|
+
expect(limiter_two).to be_overrated
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#overloaded?' do
|
151
|
+
let(:limiter) { described_class.new(:key, 1, :second) }
|
152
|
+
|
153
|
+
it 'works' do
|
154
|
+
expect(limiter.overloaded?).to be false
|
155
|
+
limiter.limit
|
156
|
+
expect(limiter.overloaded?).to be true
|
157
|
+
Timecop.freeze(1)
|
158
|
+
expect(limiter.overloaded?).to be false
|
147
159
|
end
|
148
160
|
end
|
149
161
|
|
150
|
-
|
151
|
-
|
152
|
-
|
162
|
+
describe '#to_s' do
|
163
|
+
def check(capacity, interval, expected)
|
164
|
+
expect(
|
165
|
+
described_class.new(:key, capacity, interval).to_s
|
166
|
+
).to match(expected)
|
167
|
+
end
|
153
168
|
|
154
|
-
it 'works
|
155
|
-
|
156
|
-
|
169
|
+
it 'works with symbols' do
|
170
|
+
check(1, :second, /1 per second/)
|
171
|
+
check(1, :minute, /1 per minute/)
|
172
|
+
check(1, :hour, /1 per hour/)
|
173
|
+
end
|
157
174
|
|
158
|
-
|
159
|
-
|
175
|
+
it 'works with strings' do
|
176
|
+
check(1, 'second', /1 per second/)
|
177
|
+
check(1, 'minute', /1 per minute/)
|
178
|
+
check(1, 'hour', /1 per hour/)
|
179
|
+
end
|
160
180
|
|
161
|
-
|
162
|
-
|
181
|
+
it 'works with integers' do
|
182
|
+
check(1, 1, /1 every second/)
|
183
|
+
check(1, 2, /1 every 2 seconds/)
|
184
|
+
check(2, 3, /2 every 3 seconds/)
|
163
185
|
end
|
164
186
|
end
|
165
187
|
|