berater 0.7.1 → 0.10.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.
- checksums.yaml +4 -4
- data/lib/berater/concurrency_limiter.rb +9 -13
- data/lib/berater/inhibitor.rb +4 -0
- data/lib/berater/limiter.rb +53 -21
- data/lib/berater/limiter_set.rb +66 -0
- data/lib/berater/lock.rb +2 -1
- data/lib/berater/lua_script.rb +4 -3
- data/lib/berater/rate_limiter.rb +9 -8
- data/lib/berater/rspec.rb +0 -1
- data/lib/berater/static_limiter.rb +49 -0
- data/lib/berater/test_mode.rb +27 -16
- data/lib/berater/unlimiter.rb +4 -0
- data/lib/berater/utils.rb +4 -0
- data/lib/berater/version.rb +1 -1
- data/lib/berater.rb +21 -10
- data/spec/berater_spec.rb +39 -71
- data/spec/concurrency_limiter_spec.rb +8 -2
- data/spec/inhibitor_spec.rb +10 -5
- data/spec/limiter_set_spec.rb +173 -0
- data/spec/limiter_spec.rb +97 -8
- data/spec/lua_script_spec.rb +0 -1
- data/spec/middleware_spec.rb +110 -0
- data/spec/rate_limiter_spec.rb +2 -1
- data/spec/riddle_spec.rb +1 -1
- data/spec/static_limiter_spec.rb +79 -0
- data/spec/test_mode_spec.rb +12 -6
- data/spec/unlimiter_spec.rb +11 -5
- metadata +27 -5
data/spec/berater_spec.rb
CHANGED
@@ -24,50 +24,33 @@ describe Berater do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
describe '.
|
28
|
-
|
29
|
-
let(:limiter) { Berater.new(:key, Float::INFINITY) }
|
27
|
+
describe '.limiters' do
|
28
|
+
subject { Berater.limiters }
|
30
29
|
|
31
|
-
|
32
|
-
expect(limiter).to be_a Berater::Unlimiter
|
33
|
-
expect(limiter.key).to be :key
|
34
|
-
end
|
30
|
+
let(:limiter) { Berater(:key, 1) }
|
35
31
|
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'accepts options' do
|
41
|
-
redis = double('Redis')
|
42
|
-
limiter = Berater.new(:key, Float::INFINITY, redis: redis)
|
43
|
-
expect(limiter.redis).to be redis
|
44
|
-
end
|
32
|
+
it 'provides access to predefined limiters' do
|
33
|
+
expect(Berater.limiters).to be_a Berater::LimiterSet
|
45
34
|
end
|
46
35
|
|
47
|
-
|
48
|
-
|
36
|
+
it 'resets with Berater' do
|
37
|
+
subject << limiter
|
38
|
+
is_expected.not_to be_empty
|
49
39
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
40
|
+
Berater.reset
|
41
|
+
is_expected.to be_empty
|
42
|
+
end
|
43
|
+
end
|
54
44
|
|
55
|
-
|
56
|
-
|
57
|
-
|
45
|
+
shared_examples 'a Berater' do |klass, capacity, **opts|
|
46
|
+
describe '.new' do
|
47
|
+
let(:limiter) { Berater.new(:key, capacity, **opts) }
|
58
48
|
|
59
|
-
it '
|
60
|
-
|
61
|
-
limiter = Berater.new(:key, 0, redis: redis)
|
62
|
-
expect(limiter.redis).to be redis
|
49
|
+
it 'instantiates the right class' do
|
50
|
+
expect(limiter).to be_a klass
|
63
51
|
end
|
64
|
-
end
|
65
52
|
|
66
|
-
|
67
|
-
let(:limiter) { Berater.new(:key, 1, interval: :second) }
|
68
|
-
|
69
|
-
it 'instantiates a RateLimiter' do
|
70
|
-
expect(limiter).to be_a Berater::RateLimiter
|
53
|
+
it 'sets the key' do
|
71
54
|
expect(limiter.key).to be :key
|
72
55
|
end
|
73
56
|
|
@@ -75,60 +58,45 @@ describe Berater do
|
|
75
58
|
expect(limiter.redis).to be Berater.redis
|
76
59
|
end
|
77
60
|
|
78
|
-
it 'accepts
|
79
|
-
redis = double(
|
80
|
-
limiter = Berater.new(:key,
|
61
|
+
it 'accepts an optional redis parameter' do
|
62
|
+
redis = double(Redis)
|
63
|
+
limiter = Berater.new(:key, capacity, **opts.merge(redis: redis))
|
81
64
|
expect(limiter.redis).to be redis
|
82
65
|
end
|
83
66
|
end
|
84
67
|
|
85
|
-
|
86
|
-
let(:limiter) { Berater
|
68
|
+
describe 'Berater() convenience method' do
|
69
|
+
let(:limiter) { Berater(:key, capacity, **opts) }
|
87
70
|
|
88
|
-
it '
|
89
|
-
expect(limiter).to be_a
|
90
|
-
expect(limiter.key).to be :key
|
71
|
+
it 'creates a limiter' do
|
72
|
+
expect(limiter).to be_a klass
|
91
73
|
end
|
92
74
|
|
93
|
-
it '
|
94
|
-
expect(limiter
|
75
|
+
it 'creates an equivalent limiter' do
|
76
|
+
expect(limiter).to eq Berater.new(:key, capacity, **opts)
|
95
77
|
end
|
96
78
|
|
97
|
-
|
98
|
-
|
99
|
-
limiter = Berater.new(:key, 1, redis: redis)
|
100
|
-
expect(limiter.redis).to be redis
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
79
|
+
context 'with a block' do
|
80
|
+
before { Berater.test_mode = :pass }
|
104
81
|
|
105
|
-
|
106
|
-
RSpec.shared_examples 'test convenience' do |klass, capacity, **opts|
|
107
|
-
it 'creates a limiter' do
|
108
|
-
limiter = Berater(:key, capacity, **opts)
|
109
|
-
expect(limiter).to be_a klass
|
110
|
-
end
|
82
|
+
subject { Berater(:key, capacity, **opts) { 123 } }
|
111
83
|
|
112
|
-
context 'with a block' do
|
113
84
|
it 'creates a limiter and calls limit' do
|
114
|
-
limiter = Berater(:key, capacity, **opts)
|
115
85
|
expect(klass).to receive(:new).and_return(limiter)
|
116
86
|
expect(limiter).to receive(:limit).and_call_original
|
87
|
+
subject
|
88
|
+
end
|
117
89
|
|
118
|
-
|
119
|
-
|
120
|
-
expect(res).to be true
|
121
|
-
rescue Berater::Overloaded
|
122
|
-
expect(klass).to be Berater::Inhibitor
|
123
|
-
end
|
90
|
+
it 'yields' do
|
91
|
+
is_expected.to be 123
|
124
92
|
end
|
125
93
|
end
|
126
94
|
end
|
127
|
-
|
128
|
-
include_examples 'test convenience', Berater::Unlimiter, Float::INFINITY
|
129
|
-
include_examples 'test convenience', Berater::Inhibitor, 0
|
130
|
-
include_examples 'test convenience', Berater::RateLimiter, 1, interval: :second
|
131
|
-
include_examples 'test convenience', Berater::ConcurrencyLimiter, 1
|
132
95
|
end
|
133
96
|
|
97
|
+
include_examples 'a Berater', Berater::ConcurrencyLimiter, 1, timeout: 1
|
98
|
+
include_examples 'a Berater', Berater::Inhibitor, 0
|
99
|
+
include_examples 'a Berater', Berater::RateLimiter, 1, interval: :second
|
100
|
+
include_examples 'a Berater', Berater::StaticLimiter, 1
|
101
|
+
include_examples 'a Berater', Berater::Unlimiter, Float::INFINITY
|
134
102
|
end
|
@@ -36,22 +36,28 @@ describe Berater::ConcurrencyLimiter do
|
|
36
36
|
it { expect_bad_capacity(-1) }
|
37
37
|
it { expect_bad_capacity('1') }
|
38
38
|
it { expect_bad_capacity(:one) }
|
39
|
+
it { expect_bad_capacity(Float::INFINITY) }
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
43
|
describe '#timeout' do
|
43
44
|
# see spec/utils_spec.rb
|
44
45
|
|
46
|
+
it 'defaults to nil' do
|
47
|
+
limiter = described_class.new(:key, 1)
|
48
|
+
expect(limiter.timeout).to be nil
|
49
|
+
end
|
50
|
+
|
45
51
|
it 'saves the interval in original and millisecond format' do
|
46
52
|
limiter = described_class.new(:key, 1, timeout: 3)
|
47
53
|
expect(limiter.timeout).to be 3
|
48
|
-
expect(limiter.instance_variable_get(:@
|
54
|
+
expect(limiter.instance_variable_get(:@timeout)).to be (3 * 10**3)
|
49
55
|
end
|
50
56
|
|
51
57
|
it 'handles infinity' do
|
52
58
|
limiter = described_class.new(:key, 1, timeout: Float::INFINITY)
|
53
59
|
expect(limiter.timeout).to be Float::INFINITY
|
54
|
-
expect(limiter.instance_variable_get(:@
|
60
|
+
expect(limiter.instance_variable_get(:@timeout)).to be 0
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
data/spec/inhibitor_spec.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
describe Berater::Inhibitor do
|
2
|
+
subject { described_class.new }
|
3
|
+
|
2
4
|
describe '.new' do
|
3
5
|
it 'initializes without any arguments or options' do
|
4
|
-
|
6
|
+
is_expected.to be_a described_class
|
5
7
|
end
|
6
8
|
|
7
9
|
it 'initializes with any arguments and options' do
|
@@ -9,15 +11,18 @@ describe Berater::Inhibitor do
|
|
9
11
|
end
|
10
12
|
|
11
13
|
it 'has default values' do
|
12
|
-
expect(
|
13
|
-
expect(
|
14
|
+
expect(subject.key).to be :inhibitor
|
15
|
+
expect(subject.redis).to be Berater.redis
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
19
|
describe '#limit' do
|
18
|
-
subject { described_class.new }
|
19
|
-
|
20
20
|
it_behaves_like 'it is overloaded'
|
21
21
|
end
|
22
22
|
|
23
|
+
describe '#to_s' do
|
24
|
+
it do
|
25
|
+
expect(subject.to_s).to include described_class.to_s
|
26
|
+
end
|
27
|
+
end
|
23
28
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
describe Berater::LimiterSet do
|
2
|
+
subject { described_class.new }
|
3
|
+
|
4
|
+
let(:unlimiter) { Berater::Unlimiter.new }
|
5
|
+
let(:inhibitor) { Berater::Inhibitor.new }
|
6
|
+
|
7
|
+
describe '#each' do
|
8
|
+
it 'returns an Enumerator' do
|
9
|
+
expect(subject.each).to be_a Enumerator
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'works with an empty set' do
|
13
|
+
expect(subject.each.to_a).to eq []
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns elements' do
|
17
|
+
subject << unlimiter
|
18
|
+
expect(subject.each.to_a).to eq [ unlimiter ]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#<<' do
|
23
|
+
it 'adds a limiter' do
|
24
|
+
subject << unlimiter
|
25
|
+
expect(subject.each.to_a).to eq [ unlimiter ]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'rejects things that are not limiters' do
|
29
|
+
expect {
|
30
|
+
subject << :foo
|
31
|
+
}.to raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'updates existing keys' do
|
35
|
+
limiter = Berater::Unlimiter.new
|
36
|
+
expect(limiter).to eq unlimiter
|
37
|
+
expect(limiter).not_to be unlimiter
|
38
|
+
|
39
|
+
subject << unlimiter
|
40
|
+
subject << limiter
|
41
|
+
|
42
|
+
expect(subject.each.to_a).to eq [ limiter ]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '[]=' do
|
47
|
+
it 'adds a limiter' do
|
48
|
+
subject[:key] = unlimiter
|
49
|
+
|
50
|
+
expect(subject.each.to_a).to eq [ unlimiter ]
|
51
|
+
is_expected.to include :key
|
52
|
+
is_expected.to include unlimiter
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'rejects things that are not limiters' do
|
56
|
+
expect {
|
57
|
+
subject[:key] = :foo
|
58
|
+
}.to raise_error(ArgumentError)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#[]' do
|
63
|
+
it 'returns nil for missing keys' do
|
64
|
+
expect(subject[:key]).to be nil
|
65
|
+
expect(subject[nil]).to be nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'retreives limiters' do
|
69
|
+
subject << unlimiter
|
70
|
+
expect(subject[unlimiter.key]).to be unlimiter
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#fetch' do
|
75
|
+
it 'raises for missing keys' do
|
76
|
+
expect {
|
77
|
+
subject.fetch(:key)
|
78
|
+
}.to raise_error(KeyError)
|
79
|
+
|
80
|
+
expect {
|
81
|
+
subject.fetch(nil)
|
82
|
+
}.to raise_error(KeyError)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'returns the default if provided' do
|
86
|
+
expect(subject.fetch(:key, unlimiter)).to be unlimiter
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'calls the default proc if provided' do
|
90
|
+
expect {|block| subject.fetch(:key, &block) }.to yield_control
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'retreives limiters' do
|
94
|
+
subject << unlimiter
|
95
|
+
expect(subject.fetch(unlimiter.key)).to be unlimiter
|
96
|
+
expect(subject.fetch(unlimiter.key, :default)).to be unlimiter
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#include?' do
|
101
|
+
before do
|
102
|
+
subject << unlimiter
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'works with keys' do
|
106
|
+
is_expected.to include unlimiter.key
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'works with limiters' do
|
110
|
+
is_expected.to include unlimiter
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'works when target is missing' do
|
114
|
+
is_expected.not_to include inhibitor.key
|
115
|
+
is_expected.not_to include inhibitor
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#clear' do
|
120
|
+
it 'works when empty' do
|
121
|
+
subject.clear
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'clears limiters' do
|
125
|
+
subject << unlimiter
|
126
|
+
is_expected.to include unlimiter
|
127
|
+
|
128
|
+
subject.clear
|
129
|
+
is_expected.not_to include unlimiter
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#count' do
|
134
|
+
it 'counts' do
|
135
|
+
expect(subject.count).to be 0
|
136
|
+
|
137
|
+
subject << unlimiter
|
138
|
+
expect(subject.count).to be 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '#delete' do
|
143
|
+
it 'works when the target is missing' do
|
144
|
+
subject.delete(unlimiter)
|
145
|
+
subject.delete(unlimiter.key)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'works with keys' do
|
149
|
+
subject << unlimiter
|
150
|
+
is_expected.to include unlimiter
|
151
|
+
|
152
|
+
subject.delete(unlimiter.key)
|
153
|
+
is_expected.not_to include unlimiter
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'works with limiters' do
|
157
|
+
subject << unlimiter
|
158
|
+
is_expected.to include unlimiter
|
159
|
+
|
160
|
+
subject.delete(unlimiter)
|
161
|
+
is_expected.not_to include unlimiter
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe '#empty?' do
|
166
|
+
it 'works' do
|
167
|
+
is_expected.to be_empty
|
168
|
+
|
169
|
+
subject << unlimiter
|
170
|
+
is_expected.not_to be_empty
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
data/spec/limiter_spec.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
describe Berater::Limiter do
|
2
|
-
|
3
|
-
|
2
|
+
describe '.new' do
|
3
|
+
it 'can only be called on subclasses' do
|
4
|
+
expect { described_class.new }.to raise_error(NoMethodError)
|
5
|
+
end
|
4
6
|
end
|
5
7
|
|
6
8
|
describe 'abstract methods' do
|
@@ -44,8 +46,49 @@ describe Berater::Limiter do
|
|
44
46
|
expect {
|
45
47
|
subject.limit(cost: -1)
|
46
48
|
}.to raise_error(ArgumentError)
|
49
|
+
|
50
|
+
expect {
|
51
|
+
subject.limit(cost: Float::INFINITY)
|
52
|
+
}.to raise_error(ArgumentError)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when Berater.redis is nil' do
|
57
|
+
let!(:redis) { Berater.redis }
|
58
|
+
|
59
|
+
before { Berater.redis = nil }
|
60
|
+
|
61
|
+
it 'works with Unlimiter since redis is not used' do
|
62
|
+
expect(subject.redis).to be nil
|
63
|
+
expect {|b| subject.limit(&b) }.to yield_control
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'raises when redis is needed' do
|
67
|
+
limiter = Berater::RateLimiter.new(:key, 1, :second)
|
68
|
+
expect(limiter.redis).to be nil
|
69
|
+
expect { limiter.limit }.to raise_error(RuntimeError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'works when redis is passed in' do
|
73
|
+
limiter = Berater::RateLimiter.new(:key, 1, :second, redis: redis)
|
74
|
+
expect {|b| limiter.limit(&b) }.to yield_control
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'raises when redis is bogus' do
|
78
|
+
limiter = Berater::RateLimiter.new(:key, 1, :second, redis: :stub)
|
79
|
+
expect { limiter.limit }.to raise_error(RuntimeError)
|
47
80
|
end
|
48
81
|
end
|
82
|
+
|
83
|
+
it 'releases the lock even when limited code raises an error' do
|
84
|
+
lock = Berater::Lock.new(Float::INFINITY, 0)
|
85
|
+
expect(subject).to receive(:acquire_lock).and_return(lock)
|
86
|
+
expect(lock).to receive(:release)
|
87
|
+
|
88
|
+
expect {
|
89
|
+
subject.limit { raise 'fail' }
|
90
|
+
}.to raise_error(RuntimeError)
|
91
|
+
end
|
49
92
|
end
|
50
93
|
|
51
94
|
describe '#==' do
|
@@ -61,12 +104,6 @@ describe Berater::Limiter do
|
|
61
104
|
)
|
62
105
|
end
|
63
106
|
|
64
|
-
it 'equals something with equvalent initialization parameters' do
|
65
|
-
expect(limiter).to eq(
|
66
|
-
Berater::RateLimiter.new(:key, 1, 1)
|
67
|
-
)
|
68
|
-
end
|
69
|
-
|
70
107
|
it 'does not equal something different' do
|
71
108
|
expect(limiter).not_to eq(
|
72
109
|
Berater::RateLimiter.new(:key, 2, :second)
|
@@ -104,4 +141,56 @@ describe Berater::Limiter do
|
|
104
141
|
end
|
105
142
|
end
|
106
143
|
|
144
|
+
describe '#cache_key' do
|
145
|
+
subject { klass.new(:key).send(:cache_key) }
|
146
|
+
|
147
|
+
context 'with Unlimiter' do
|
148
|
+
let(:klass) { Berater::Unlimiter }
|
149
|
+
|
150
|
+
it do
|
151
|
+
is_expected.to eq 'Berater:Unlimiter:key'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context 'with custom limiter' do
|
156
|
+
MyLimiter = Class.new(Berater::Unlimiter)
|
157
|
+
|
158
|
+
let(:klass) { MyLimiter }
|
159
|
+
|
160
|
+
it 'adds Berater prefix' do
|
161
|
+
is_expected.to eq 'Berater:MyLimiter:key'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '.cache_key' do
|
167
|
+
subject { klass.send(:cache_key, :key) }
|
168
|
+
|
169
|
+
context 'with Unlimiter' do
|
170
|
+
let(:klass) { Berater::Unlimiter }
|
171
|
+
|
172
|
+
it do
|
173
|
+
is_expected.to eq 'Berater:Unlimiter:key'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'with custom limiter' do
|
178
|
+
MyLimiter = Class.new(Berater::Unlimiter)
|
179
|
+
|
180
|
+
let(:klass) { MyLimiter }
|
181
|
+
|
182
|
+
it 'adds Berater prefix' do
|
183
|
+
is_expected.to eq 'Berater:MyLimiter:key'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe '.inherited' do
|
189
|
+
it 'creates convenience methods' do
|
190
|
+
expect(Berater.method(:Unlimiter)).to be_a Method
|
191
|
+
expect(Berater::Unlimiter()).to be_a Berater::Unlimiter
|
192
|
+
expect {|b| Berater::Unlimiter(&b) }.to yield_control
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
107
196
|
end
|
data/spec/lua_script_spec.rb
CHANGED
@@ -0,0 +1,110 @@
|
|
1
|
+
class Meddler
|
2
|
+
def call(*args, **kwargs)
|
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(limiter, **opts)
|
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(limiter, **opts)
|
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
@@ -37,6 +37,7 @@ describe Berater::RateLimiter do
|
|
37
37
|
it { expect_bad_capacity(-1) }
|
38
38
|
it { expect_bad_capacity('1') }
|
39
39
|
it { expect_bad_capacity(:one) }
|
40
|
+
it { expect_bad_capacity(Float::INFINITY) }
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -47,7 +48,7 @@ describe Berater::RateLimiter do
|
|
47
48
|
|
48
49
|
it 'saves the interval in original and millisecond format' do
|
49
50
|
expect(subject.interval).to be :second
|
50
|
-
expect(subject.instance_variable_get(:@
|
51
|
+
expect(subject.instance_variable_get(:@interval)).to be 10**3
|
51
52
|
end
|
52
53
|
|
53
54
|
it 'must be > 0' do
|
data/spec/riddle_spec.rb
CHANGED