pause 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +31 -0
- data/.github/workflows/rubocop.yml +31 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +28 -0
- data/.rubocop_todo.yml +192 -0
- data/Gemfile +16 -1
- data/Gemfile.lock +145 -0
- data/Guardfile +7 -6
- data/LICENSE.txt +1 -1
- data/README.md +50 -30
- data/Rakefile +11 -10
- data/bin/spec +14 -0
- data/lib/pause/action.rb +6 -5
- data/lib/pause/analyzer.rb +4 -0
- data/lib/pause/configuration.rb +3 -1
- data/lib/pause/helper/timing.rb +2 -0
- data/lib/pause/logger.rb +12 -5
- data/lib/pause/rate_limited_event.rb +2 -1
- data/lib/pause/redis/adapter.rb +12 -10
- data/lib/pause/redis/sharded_adapter.rb +3 -2
- data/lib/pause/version.rb +3 -1
- data/lib/pause.rb +14 -11
- data/pause.gemspec +14 -17
- data/spec/pause/action_spec.rb +48 -42
- data/spec/pause/analyzer_spec.rb +9 -10
- data/spec/pause/configuration_spec.rb +6 -5
- data/spec/pause/logger_spec.rb +25 -0
- data/spec/pause/pause_spec.rb +6 -4
- data/spec/pause/redis/adapter_spec.rb +54 -35
- data/spec/pause/redis/sharded_adapter_spec.rb +10 -10
- data/spec/spec_helper.rb +11 -7
- metadata +19 -120
- data/.travis.yml +0 -14
data/lib/pause/action.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pause
|
2
4
|
class Action
|
3
5
|
attr_accessor :identifier
|
4
6
|
|
5
|
-
def initialize(identifier)
|
6
|
-
@identifier
|
7
|
+
def initialize(identifier, &block)
|
8
|
+
@identifier = identifier
|
7
9
|
self.class.checks ||= []
|
10
|
+
instance_exec(&block) if block
|
8
11
|
end
|
9
12
|
|
10
13
|
def scope
|
@@ -22,9 +25,8 @@ module Pause
|
|
22
25
|
# scope "my:scope"
|
23
26
|
# end
|
24
27
|
#
|
25
|
-
@scope = klass.name.downcase.gsub(
|
28
|
+
@scope = klass.name.downcase.gsub('::', '.')
|
26
29
|
class << self
|
27
|
-
|
28
30
|
# @param [String] args
|
29
31
|
def scope(*args)
|
30
32
|
@scope = args.first if args && args.size == 1
|
@@ -156,6 +158,5 @@ module Pause
|
|
156
158
|
def adapter
|
157
159
|
self.class.adapter
|
158
160
|
end
|
159
|
-
|
160
161
|
end
|
161
162
|
end
|
data/lib/pause/analyzer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pause/helper/timing'
|
2
4
|
|
3
5
|
module Pause
|
@@ -12,12 +14,14 @@ module Pause
|
|
12
14
|
# @return [Pause::RateLimitedEvent] the action was blocked as a result of this check
|
13
15
|
def check(action, recalculate: false)
|
14
16
|
return false if adapter.rate_limited?(action.scope, action.identifier) && !recalculate
|
17
|
+
|
15
18
|
timestamp = period_marker(Pause.config.resolution, Time.now.to_i)
|
16
19
|
set = adapter.key_history(action.scope, action.identifier)
|
17
20
|
action.checks.each do |period_check|
|
18
21
|
start_time = timestamp - period_check.period_seconds
|
19
22
|
set.reverse.inject(0) do |sum, element|
|
20
23
|
break if element.ts < start_time
|
24
|
+
|
21
25
|
sum += element.count
|
22
26
|
if sum >= period_check.max_allowed
|
23
27
|
adapter.rate_limit!(action.scope, action.identifier, period_check.block_ttl)
|
data/lib/pause/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pause
|
2
4
|
class Configuration
|
3
5
|
attr_writer :redis_host, :redis_port, :redis_db, :resolution, :history, :sharded
|
@@ -24,7 +26,7 @@ module Pause
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def history
|
27
|
-
(@history ||
|
29
|
+
(@history || 86_400).to_i
|
28
30
|
end
|
29
31
|
|
30
32
|
def sharded
|
data/lib/pause/helper/timing.rb
CHANGED
data/lib/pause/logger.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pause
|
4
|
+
# @description Logger class for Pause
|
2
5
|
class Logger
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
+
class << self
|
7
|
+
def puts(message)
|
8
|
+
$stdout.puts message
|
9
|
+
end
|
6
10
|
|
7
|
-
|
8
|
-
|
11
|
+
def fatal(message)
|
12
|
+
# rubocop: disable Style/StderrPuts
|
13
|
+
$stderr.puts message.red
|
14
|
+
# rubocop: enable Style/StderrPuts
|
15
|
+
end
|
9
16
|
end
|
10
17
|
end
|
11
18
|
end
|
data/lib/pause/redis/adapter.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pause/helper/timing'
|
2
4
|
|
3
5
|
module Pause
|
4
6
|
module Redis
|
5
|
-
|
6
7
|
# This class encapsulates Redis operations used by Pause
|
7
8
|
class Adapter
|
8
9
|
class << self
|
9
10
|
def redis
|
10
|
-
@
|
11
|
+
@redis ||= ::Redis.new(redis_connection_opts)
|
11
12
|
end
|
12
13
|
|
13
14
|
def redis_connection_opts
|
14
15
|
{ host: Pause.config.redis_host,
|
15
16
|
port: Pause.config.redis_port,
|
16
|
-
db:
|
17
|
+
db: Pause.config.redis_db }
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
@@ -72,6 +73,7 @@ module Pause
|
|
72
73
|
# @return count [Integer] the number of items deleted
|
73
74
|
def delete_rate_limited_keys(scope)
|
74
75
|
return 0 unless rate_limited_keys(scope).any?
|
76
|
+
|
75
77
|
delete_tracking_keys(scope, rate_limited_keys(scope))
|
76
78
|
redis.zremrangebyscore(rate_limited_list(scope), '-inf', '+inf').tap do |_count|
|
77
79
|
redis.del rate_limited_list(scope)
|
@@ -84,7 +86,7 @@ module Pause
|
|
84
86
|
end
|
85
87
|
|
86
88
|
def disable(scope)
|
87
|
-
redis.set("internal:|#{scope}|:disabled",
|
89
|
+
redis.set("internal:|#{scope}|:disabled", '1')
|
88
90
|
end
|
89
91
|
|
90
92
|
def enable(scope)
|
@@ -110,11 +112,11 @@ module Pause
|
|
110
112
|
end
|
111
113
|
|
112
114
|
def truncate_set_for(k)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
return unless redis.zcard(k) > time_blocks_to_keep
|
116
|
+
|
117
|
+
list = extract_set_elements(k)
|
118
|
+
to_remove = list.slice(0, (list.size - time_blocks_to_keep)).map(&:ts)
|
119
|
+
redis.zrem(k, to_remove) if k && to_remove&.size&.positive?
|
118
120
|
end
|
119
121
|
|
120
122
|
def delete_tracking_keys(scope, ids)
|
@@ -137,7 +139,7 @@ module Pause
|
|
137
139
|
|
138
140
|
def keys(key_scope)
|
139
141
|
redis.keys("#{key_scope}:*").map do |key|
|
140
|
-
key.gsub(/^#{key_scope}:/,
|
142
|
+
key.gsub(/^#{key_scope}:/, '').tr('|', '')
|
141
143
|
end
|
142
144
|
end
|
143
145
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pause
|
2
4
|
module Redis
|
3
5
|
class OperationNotSupported < StandardError
|
@@ -7,7 +9,6 @@ module Pause
|
|
7
9
|
# Operations that are not possible when data is sharded
|
8
10
|
# raise an error.
|
9
11
|
class ShardedAdapter < Adapter
|
10
|
-
|
11
12
|
# Overrides real multi which is not possible when sharded.
|
12
13
|
def with_multi
|
13
14
|
yield(redis) if block_given?
|
@@ -23,7 +24,7 @@ module Pause
|
|
23
24
|
private
|
24
25
|
|
25
26
|
def keys(_key_scope)
|
26
|
-
raise OperationNotSupported
|
27
|
+
raise OperationNotSupported, 'Can not be executed when Pause is configured in sharded mode'
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|
data/lib/pause/version.rb
CHANGED
data/lib/pause.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'redis'
|
4
|
+
require 'colored2'
|
2
5
|
require 'pause/version'
|
3
6
|
require 'pause/configuration'
|
4
7
|
require 'pause/action'
|
@@ -9,15 +12,15 @@ require 'pause/redis/sharded_adapter'
|
|
9
12
|
require 'pause/rate_limited_event'
|
10
13
|
|
11
14
|
module Pause
|
12
|
-
|
15
|
+
PeriodCheck = Struct.new(:period_seconds, :max_allowed, :block_ttl) do
|
13
16
|
def <=>(other)
|
14
|
-
|
17
|
+
period_seconds <=> other.period_seconds
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
18
|
-
|
21
|
+
SetElement = Struct.new(:ts, :count) do
|
19
22
|
def <=>(other)
|
20
|
-
|
23
|
+
ts <=> other.ts
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
@@ -27,17 +30,17 @@ module Pause
|
|
27
30
|
end
|
28
31
|
|
29
32
|
def adapter
|
30
|
-
@adapter ||= config.sharded
|
31
|
-
|
32
|
-
|
33
|
+
@adapter ||= if config.sharded
|
34
|
+
Pause::Redis::ShardedAdapter.new(config)
|
35
|
+
else
|
36
|
+
Pause::Redis::Adapter.new(config)
|
37
|
+
end
|
33
38
|
end
|
34
39
|
|
35
|
-
|
36
|
-
@adapter = adapter
|
37
|
-
end
|
40
|
+
attr_writer :adapter
|
38
41
|
|
39
42
|
def configure(&block)
|
40
|
-
@
|
43
|
+
@configure ||= Pause::Configuration.new.configure(&block)
|
41
44
|
end
|
42
45
|
|
43
46
|
def config(&block)
|
data/pause.gemspec
CHANGED
@@ -1,30 +1,27 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
lib = File.expand_path('lib', __dir__)
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
6
|
require 'pause/version'
|
5
7
|
|
6
8
|
Gem::Specification.new do |gem|
|
7
9
|
gem.name = 'pause'
|
8
10
|
gem.version = Pause::VERSION
|
9
|
-
gem.authors = ['Atasay Gokkaya', 'Paul Henry', 'Eric Saxby'
|
10
|
-
gem.email = %w
|
11
|
-
gem.summary =
|
12
|
-
gem.description =
|
11
|
+
gem.authors = ['Konstantin Gredeskoul', 'Atasay Gokkaya', 'Paul Henry', 'Eric Saxby']
|
12
|
+
gem.email = %w[kigster@gmail.com atasay@wanelo.com paul@wanelo.com sax@ericsaxby.com]
|
13
|
+
gem.summary = 'Fast, scalable, and flexible real time rate limiting library for distributed Ruby environments backed by Redis.'
|
14
|
+
gem.description = 'This gem provides highly flexible and easy to use interface to define rate limit checks, register events as they come, and verify if the rate limit is reached. Multiple checks for the same metric are easily supported. This gem is used at very high scale on several popular web sites.'
|
13
15
|
gem.homepage = 'https://github.com/kigster/pause'
|
14
16
|
|
15
|
-
gem.files = `git ls-files`.split(
|
16
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
18
19
|
gem.require_paths = ['lib']
|
19
20
|
|
21
|
+
gem.add_dependency 'colored2'
|
20
22
|
gem.add_dependency 'redis'
|
21
|
-
gem.add_dependency 'hiredis'
|
22
23
|
|
23
|
-
|
24
|
-
gem.
|
25
|
-
gem.
|
26
|
-
gem.add_development_dependency 'fakeredis'
|
27
|
-
gem.add_development_dependency 'guard-rspec'
|
28
|
-
gem.add_development_dependency 'timecop'
|
29
|
-
gem.add_development_dependency 'rake'
|
24
|
+
# optional
|
25
|
+
# gem.add_dependency 'hiredis'
|
26
|
+
gem.metadata['rubygems_mfa_required'] = 'true'
|
30
27
|
end
|
data/spec/pause/action_spec.rb
CHANGED
@@ -11,6 +11,10 @@ describe Pause::Action do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
let(:resolution) { 10 }
|
14
|
+
let(:identifier) { '11112222' }
|
15
|
+
let(:action) { MyNotification.new(identifier) }
|
16
|
+
let(:other_identifier) { '8798734' }
|
17
|
+
let(:other_action) { MyNotification.new(other_identifier) }
|
14
18
|
let(:history) { 60 }
|
15
19
|
let(:configuration) { Pause::Configuration.new }
|
16
20
|
let(:adapter) { Pause::Redis::Adapter.new(Pause.config) }
|
@@ -22,15 +26,9 @@ describe Pause::Action do
|
|
22
26
|
allow(Pause).to receive(:adapter).and_return(adapter)
|
23
27
|
end
|
24
28
|
|
25
|
-
let(:identifier) { '11112222' }
|
26
|
-
let(:action) { MyNotification.new(identifier) }
|
27
|
-
|
28
|
-
let(:other_identifier) { '8798734' }
|
29
|
-
let(:other_action) { MyNotification.new(other_identifier) }
|
30
|
-
|
31
29
|
RSpec.shared_examples 'an action' do
|
32
30
|
describe '#increment!' do
|
33
|
-
it '
|
31
|
+
it 'increments' do
|
34
32
|
time = Time.now
|
35
33
|
Timecop.freeze time do
|
36
34
|
expect(Pause.adapter).to receive(:increment).with(action.scope, identifier, time.to_i, 1)
|
@@ -40,7 +38,7 @@ describe Pause::Action do
|
|
40
38
|
end
|
41
39
|
|
42
40
|
describe '#ok?' do
|
43
|
-
it '
|
41
|
+
it 'successfullies return if the action is blocked or not' do
|
44
42
|
time = Time.now
|
45
43
|
Timecop.freeze time do
|
46
44
|
4.times do
|
@@ -52,7 +50,7 @@ describe Pause::Action do
|
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
|
-
it '
|
53
|
+
it 'successfullies consider different period checks' do
|
56
54
|
time = Time.parse('Sept 22, 11:34:00')
|
57
55
|
|
58
56
|
Timecop.freeze time - 30 do
|
@@ -71,7 +69,7 @@ describe Pause::Action do
|
|
71
69
|
end
|
72
70
|
end
|
73
71
|
|
74
|
-
it '
|
72
|
+
it 'returns false and silently fail if redis is not available' do
|
75
73
|
allow(Pause::Logger).to receive(:fatal)
|
76
74
|
allow_any_instance_of(Redis).to receive(:zrange).and_raise Redis::CannotConnectError
|
77
75
|
time = period_marker(resolution, Time.now.to_i)
|
@@ -86,7 +84,7 @@ describe Pause::Action do
|
|
86
84
|
context 'action should not be rate limited' do
|
87
85
|
it 'returns nil' do
|
88
86
|
expect(adapter.rate_limited?(action.scope, action.identifier)).to be false
|
89
|
-
expect(action.analyze).to
|
87
|
+
expect(action.analyze).to be_nil
|
90
88
|
end
|
91
89
|
end
|
92
90
|
|
@@ -130,7 +128,7 @@ describe Pause::Action do
|
|
130
128
|
end
|
131
129
|
|
132
130
|
context 'actions under test' do
|
133
|
-
['123456', 'hello', 0,
|
131
|
+
['123456', 'hello', 0, 999_999].each do |id|
|
134
132
|
let(:identifier) { id }
|
135
133
|
let(:action) { MyNotification.new(identifier) }
|
136
134
|
describe "action with identifier #{id}" do
|
@@ -151,38 +149,47 @@ describe Pause::Action do
|
|
151
149
|
let(:bogus) { Struct.new(:name, :event).new }
|
152
150
|
|
153
151
|
describe '#unless_rate_limited' do
|
154
|
-
before
|
155
|
-
|
152
|
+
before { expect(bogus).to receive(:name).twice }
|
153
|
+
|
154
|
+
describe '#initialize' do
|
155
|
+
before { expect(bogus).to receive(:event).once }
|
156
|
+
|
157
|
+
it 'is able to use methods inside the #new block' do
|
158
|
+
b = bogus
|
159
|
+
CowRateLimited.new(identifier) do
|
160
|
+
unless_rate_limited { b.name } # this executes
|
161
|
+
unless_rate_limited { b.name } # this also executes
|
162
|
+
unless_rate_limited { b.name } # and this will be rate limited
|
163
|
+
if_rate_limited { b.event }
|
164
|
+
end
|
165
|
+
end
|
156
166
|
end
|
157
|
-
|
167
|
+
|
168
|
+
it 'calls through the block' do
|
158
169
|
action.unless_rate_limited { bogus.name }
|
159
170
|
action.unless_rate_limited { bogus.name }
|
160
171
|
result = action.unless_rate_limited { bogus.name }
|
161
|
-
expect(result).to
|
172
|
+
expect(result).to be_a(Pause::RateLimitedEvent)
|
162
173
|
end
|
163
|
-
end
|
164
|
-
|
165
|
-
describe '#unless_rate_limited' do
|
166
|
-
before { expect(bogus).to receive(:name).exactly(2).times }
|
167
174
|
|
168
|
-
it '
|
175
|
+
it 'calls through the block' do
|
169
176
|
3.times { action.unless_rate_limited { bogus.name } }
|
170
177
|
end
|
171
178
|
|
172
179
|
describe '#if_rate_limited' do
|
173
180
|
before { 2.times { action.unless_rate_limited { bogus.name } } }
|
174
181
|
|
175
|
-
it '
|
182
|
+
it 'does not analyze during method call' do
|
176
183
|
bogus.event = 1
|
177
184
|
action.if_rate_limited { |event| bogus.event = event }
|
178
|
-
expect(bogus.event).to
|
185
|
+
expect(bogus.event).to be_a(Pause::RateLimitedEvent)
|
179
186
|
expect(bogus.event.identifier).to eq(identifier)
|
180
187
|
end
|
181
188
|
|
182
|
-
it '
|
189
|
+
it 'analyzes if requested' do
|
183
190
|
action.unless_rate_limited { bogus.name }
|
184
191
|
result = action.if_rate_limited { |event| bogus.event = event }
|
185
|
-
expect(bogus.event).to
|
192
|
+
expect(bogus.event).to be_a(Pause::RateLimitedEvent)
|
186
193
|
expect(result).to eq(bogus.event)
|
187
194
|
end
|
188
195
|
end
|
@@ -190,7 +197,7 @@ describe Pause::Action do
|
|
190
197
|
end
|
191
198
|
|
192
199
|
describe '.tracked_identifiers' do
|
193
|
-
it '
|
200
|
+
it 'returns all the identifiers tracked (but not blocked) so far' do
|
194
201
|
action.increment!
|
195
202
|
other_action.increment!
|
196
203
|
|
@@ -203,7 +210,7 @@ describe Pause::Action do
|
|
203
210
|
end
|
204
211
|
|
205
212
|
describe '.rate_limited_identifiers' do
|
206
|
-
it '
|
213
|
+
it 'returns all the identifiers blocked' do
|
207
214
|
action.increment!(100, Time.now.to_i)
|
208
215
|
other_action.increment!(100, Time.now.to_i)
|
209
216
|
|
@@ -216,7 +223,7 @@ describe Pause::Action do
|
|
216
223
|
end
|
217
224
|
|
218
225
|
describe '.unblock_all' do
|
219
|
-
it '
|
226
|
+
it 'unblocks all the identifiers for a scope' do
|
220
227
|
10.times { action.increment! }
|
221
228
|
other_action.increment!
|
222
229
|
|
@@ -232,7 +239,6 @@ describe Pause::Action do
|
|
232
239
|
expect(MyNotification.tracked_identifiers).to eq([other_action.identifier])
|
233
240
|
end
|
234
241
|
end
|
235
|
-
|
236
242
|
end
|
237
243
|
|
238
244
|
describe Pause::Action, '.check' do
|
@@ -250,22 +256,22 @@ describe Pause::Action, '.check' do
|
|
250
256
|
check period_seconds: 50, block_ttl: 60, max_allowed: 100
|
251
257
|
end
|
252
258
|
|
253
|
-
it '
|
259
|
+
it 'defines a period check on new instances' do
|
254
260
|
expect(ActionWithCheck.new('id').checks).to eq([
|
255
261
|
Pause::PeriodCheck.new(100, 150, 200)
|
256
262
|
])
|
257
263
|
end
|
258
264
|
|
259
|
-
it '
|
265
|
+
it 'defines a period check on new instances' do
|
260
266
|
expect(ActionWithMultipleChecks.new('id').checks).to \
|
261
267
|
eq([
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
268
|
+
Pause::PeriodCheck.new(100, 150, 200),
|
269
|
+
Pause::PeriodCheck.new(200, 150, 200),
|
270
|
+
Pause::PeriodCheck.new(300, 150, 200)
|
271
|
+
])
|
266
272
|
end
|
267
273
|
|
268
|
-
it '
|
274
|
+
it 'accepts hash arguments' do
|
269
275
|
expect(ActionWithHashChecks.new('id').checks).to eq([
|
270
276
|
Pause::PeriodCheck.new(50, 100, 60)
|
271
277
|
])
|
@@ -278,7 +284,7 @@ describe Pause::Action, '.scope' do
|
|
278
284
|
end
|
279
285
|
end
|
280
286
|
|
281
|
-
it '
|
287
|
+
it 'raises if scope is not defined' do
|
282
288
|
expect(MyApp::NoScope.new('1.2.3.4').scope).to eq 'myapp.noscope'
|
283
289
|
end
|
284
290
|
|
@@ -286,7 +292,7 @@ describe Pause::Action, '.scope' do
|
|
286
292
|
scope 'my:scope'
|
287
293
|
end
|
288
294
|
|
289
|
-
it '
|
295
|
+
it 'sets scope on class' do
|
290
296
|
expect(DefinedScopeAction.new('1.2.3.4').scope).to eq('my:scope')
|
291
297
|
end
|
292
298
|
end
|
@@ -309,26 +315,26 @@ describe Pause::Action, 'enabled/disabled states' do
|
|
309
315
|
describe '#disable' do
|
310
316
|
before do
|
311
317
|
expect(action).to be_enabled
|
312
|
-
expect(action).
|
318
|
+
expect(action).not_to be_disabled
|
313
319
|
action.disable
|
314
320
|
end
|
315
321
|
|
316
322
|
it 'disables the action' do
|
317
323
|
expect(action).to be_disabled
|
318
|
-
expect(action).
|
324
|
+
expect(action).not_to be_enabled
|
319
325
|
end
|
320
326
|
end
|
321
327
|
|
322
328
|
describe '#enable' do
|
323
329
|
before do
|
324
330
|
action.disable
|
325
|
-
expect(action).
|
331
|
+
expect(action).not_to be_enabled
|
326
332
|
action.enable
|
327
333
|
end
|
328
334
|
|
329
335
|
it 'enables the action' do
|
330
336
|
expect(action).to be_enabled
|
331
|
-
expect(action).
|
337
|
+
expect(action).not_to be_disabled
|
332
338
|
end
|
333
339
|
end
|
334
340
|
end
|
data/spec/pause/analyzer_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'timecop'
|
3
5
|
|
@@ -11,20 +13,17 @@ describe Pause::Analyzer do
|
|
11
13
|
end
|
12
14
|
|
13
15
|
let(:resolution) { 10 }
|
16
|
+
let(:analyzer) { Pause.analyzer }
|
17
|
+
let(:action) { FollowPushNotification.new('1243123') }
|
14
18
|
let(:history) { 60 }
|
15
19
|
let(:configuration) { Pause::Configuration.new }
|
16
20
|
let(:adapter) { Pause::Redis::Adapter.new(configuration) }
|
17
21
|
|
18
22
|
before do
|
19
|
-
allow(Pause).to
|
20
|
-
allow(Pause
|
21
|
-
allow(Pause.config).to receive(:history).and_return(history)
|
22
|
-
allow(Pause).to receive(:adapter).and_return(adapter)
|
23
|
+
allow(Pause.config).to receive_messages(resolution: resolution, history: history)
|
24
|
+
allow(Pause).to receive_messages(config: configuration, adapter: adapter)
|
23
25
|
end
|
24
26
|
|
25
|
-
let(:analyzer) { Pause.analyzer }
|
26
|
-
let(:action) { FollowPushNotification.new('1243123') }
|
27
|
-
|
28
27
|
describe '#analyze' do
|
29
28
|
it 'checks and blocks if max_allowed is reached' do
|
30
29
|
time = Time.now
|
@@ -39,11 +38,11 @@ describe Pause::Analyzer do
|
|
39
38
|
end
|
40
39
|
|
41
40
|
describe '#check' do
|
42
|
-
it '
|
43
|
-
expect(analyzer.check(action)).to
|
41
|
+
it 'returns nil if action is NOT blocked' do
|
42
|
+
expect(analyzer.check(action)).to be_nil
|
44
43
|
end
|
45
44
|
|
46
|
-
it '
|
45
|
+
it 'returns blocked action if action is blocked' do
|
47
46
|
Timecop.freeze Time.now do
|
48
47
|
5.times do
|
49
48
|
action.increment!
|
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Pause::Configuration, '#configure' do
|
6
|
+
subject { described_class.new }
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
it 'should allow configuration via block' do
|
8
|
+
it 'allows configuration via block' do
|
8
9
|
subject.configure do |c|
|
9
10
|
c.redis_host = '128.23.12.8'
|
10
11
|
c.redis_port = '2134'
|
@@ -24,7 +25,7 @@ describe Pause::Configuration, '#configure' do
|
|
24
25
|
expect(subject.sharded).to be true
|
25
26
|
end
|
26
27
|
|
27
|
-
it '
|
28
|
+
it 'provides redis defaults' do
|
28
29
|
subject.configure do |config|
|
29
30
|
# do nothing
|
30
31
|
end
|
@@ -33,7 +34,7 @@ describe Pause::Configuration, '#configure' do
|
|
33
34
|
expect(subject.redis_port).to eq(6379)
|
34
35
|
expect(subject.redis_db).to eq('1')
|
35
36
|
expect(subject.resolution).to eq(600) # 10 minutes
|
36
|
-
expect(subject.history).to eq(
|
37
|
+
expect(subject.history).to eq(86_400) # one day
|
37
38
|
expect(subject.sharded).to be false
|
38
39
|
end
|
39
40
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Pause::Logger do
|
6
|
+
describe 'when accessed #puts' do
|
7
|
+
before do
|
8
|
+
expect($stdout).to receive(:puts).with('hello')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'calls through to puts without color' do
|
12
|
+
described_class.puts('hello')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'when accessed via #fatal' do
|
17
|
+
before do
|
18
|
+
expect($stderr).to receive(:puts).with("\e[31mwhoops\e[0m")
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'calls through to puts with color' do
|
22
|
+
described_class.fatal('whoops')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|