spec/pause/action_spec.rb in pause-0.2.1 vs spec/pause/action_spec.rb in pause-0.4.0

- old
+ new

@@ -20,90 +20,173 @@ allow(Pause.config).to receive(:resolution).and_return(resolution) allow(Pause.config).to receive(:history).and_return(history) allow(Pause).to receive(:adapter).and_return(adapter) end - let(:action) { MyNotification.new('1237612') } - let(:other_action) { MyNotification.new('1237613') } + let(:identifier) { '11112222' } + let(:action) { MyNotification.new(identifier) } - describe '#increment!' do - it 'should increment' do - time = Time.now - Timecop.freeze time do - expect(Pause.adapter).to receive(:increment).with(action.scope, '1237612', time.to_i, 1) - action.increment! + let(:other_identifier) { '8798734' } + let(:other_action) { MyNotification.new(other_identifier) } + + RSpec.shared_examples 'an action' do + describe '#increment!' do + it 'should increment' do + time = Time.now + Timecop.freeze time do + expect(Pause.adapter).to receive(:increment).with(action.scope, identifier, time.to_i, 1) + action.increment! + end end end - end - describe '#ok?' do - it 'should successfully return if the action is blocked or not' do - time = Time.now - Timecop.freeze time do - 4.times do + describe '#ok?' do + it 'should successfully return if the action is blocked or not' do + time = Time.now + Timecop.freeze time do + 4.times do + action.increment! + expect(action.ok?).to be true + end action.increment! + expect(action.ok?).to be false + end + end + + it 'should successfully consider different period checks' do + time = Time.parse('Sept 22, 11:34:00') + + Timecop.freeze time - 30 do + action.increment! 4 expect(action.ok?).to be true end - action.increment! + + Timecop.freeze time do + action.increment! 2 + expect(action.ok?).to be true + end + + Timecop.freeze time do + action.increment! 1 + expect(action.ok?).to be false + end + end + + it 'should return false and silently fail if redis is not available' do + allow(Pause::Logger).to receive(:fatal) + allow_any_instance_of(Redis).to receive(:zrange).and_raise Redis::CannotConnectError + time = period_marker(resolution, Time.now.to_i) + + action.increment! 4, time - 25 + expect(action.ok?).to be false end end - it 'should successfully consider different period checks' do - time = Time.parse('Sept 22, 11:34:00') + describe '#analyze' do + context 'action should not be rate limited' do + it 'returns nil' do + expect(adapter.rate_limited?(action.scope, action.identifier)).to be false + expect(action.analyze).to be nil + end + end - Timecop.freeze time - 30 do - action.increment! 4 - expect(action.ok?).to be true + context 'action should be rate limited' do + it 'returns a RateLimitedEvent object' do + time = Time.now + rate_limit = nil + + Timecop.freeze time do + 7.times { action.increment! } + rate_limit = action.analyze + end + + expected_rate_limit = Pause::RateLimitedEvent.new(action, action.checks[0], 7, time.to_i) + + expect(rate_limit).to be_a(Pause::RateLimitedEvent) + expect(rate_limit.identifier).to eq(expected_rate_limit.identifier) + expect(rate_limit.sum).to eq(expected_rate_limit.sum) + expect(rate_limit.period_check).to eq(expected_rate_limit.period_check) + expect(rate_limit.timestamp).to eq(expected_rate_limit.timestamp) + end end + end - Timecop.freeze time do - action.increment! 2 + describe '#unblock' do + it 'unblocks the specified id' do + 10.times { action.increment! } + expect(action.ok?).to be false + action.unblock expect(action.ok?).to be true end + end - Timecop.freeze time do - action.increment! 1 + describe '#block_for' do + it 'blocks the IP for N seconds' do + expect(adapter).to receive(:rate_limit!).with(action.scope, action.identifier, 10).and_call_original + action.block_for(10) expect(action.ok?).to be false end end + end - it 'should return false and silently fail if redis is not available' do - allow(Pause::Logger).to receive(:fatal) - allow_any_instance_of(Redis).to receive(:zrange).and_raise Redis::CannotConnectError - time = period_marker(resolution, Time.now.to_i) - - action.increment! 4, time - 25 - - expect(action.ok?).to be false + context 'actions under test' do + ['123456', 'hello', 0, 999999].each do |id| + let(:identifier) { id } + let(:action) { MyNotification.new(identifier) } + describe "action with identifier #{id}" do + it_behaves_like 'an action' + end end end - describe '#analyze' do - context 'action should not be rate limited' do - it 'returns nil' do - expect(action.analyze).to be nil + context 'DSL usage' do + class CowRateLimited < Pause::Action + scope 'cow:moo' + check period_seconds: 10, max_allowed: 2, block_ttl: 40 + check period_seconds: 20, max_allowed: 4, block_ttl: 40 + end + + let(:identifier) { 'cow-moo' } + let(:action) { CowRateLimited.new(identifier) } + let(:bogus) { Struct.new(:name, :event).new } + + describe '#unless_rate_limited' do + before do + expect(bogus).to receive(:name).exactly(2).times end + it 'should call through the block' do + action.unless_rate_limited { bogus.name } + action.unless_rate_limited { bogus.name } + result = action.unless_rate_limited { bogus.name } + expect(result).to be_a_kind_of(::Pause::RateLimitedEvent) + end end - context 'action should be rate limited' do - it 'returns a RateLimitedEvent object' do - time = Time.now - rate_limit = nil + describe '#unless_rate_limited' do + before { expect(bogus).to receive(:name).exactly(2).times } - Timecop.freeze time do - 7.times { action.increment! } - rate_limit = action.analyze - end + it 'should call through the block' do + 3.times { action.unless_rate_limited { bogus.name } } + end - expected_rate_limit = Pause::RateLimitedEvent.new(action, action.checks[0], 7, time.to_i) + describe '#if_rate_limited' do + before { 2.times { action.unless_rate_limited { bogus.name } } } - expect(rate_limit).to be_a(Pause::RateLimitedEvent) - expect(rate_limit.identifier).to eq(expected_rate_limit.identifier) - expect(rate_limit.sum).to eq(expected_rate_limit.sum) - expect(rate_limit.period_check).to eq(expected_rate_limit.period_check) - expect(rate_limit.timestamp).to eq(expected_rate_limit.timestamp) + it 'it should not analyze during method call' do + bogus.event = 1 + action.if_rate_limited { |event| bogus.event = event } + expect(bogus.event).to be_a_kind_of(::Pause::RateLimitedEvent) + expect(bogus.event.identifier).to eq(identifier) + end + + it 'should analyze if requested' do + action.unless_rate_limited { bogus.name } + result = action.if_rate_limited { |event| bogus.event = event } + expect(bogus.event).to be_a_kind_of(::Pause::RateLimitedEvent) + expect(result).to eq(bogus.event) + end end end end describe '.tracked_identifiers' do @@ -148,29 +231,10 @@ expect(MyNotification.rate_limited_identifiers).to be_empty expect(MyNotification.tracked_identifiers).to eq([other_action.identifier]) end end - describe '#unblock' do - it 'unblocks the specified id' do - 10.times { action.increment! } - - expect(action.ok?).to be false - - action.unblock - - expect(action.ok?).to be true - end - end - - describe '#block_for' do - it 'blocks the IP for N seconds' do - expect(adapter).to receive(:rate_limit!).with(action.scope, action.identifier, 10).and_call_original - action.block_for(10) - expect(action.ok?).to be false - end - end end describe Pause::Action, '.check' do class ActionWithCheck < Pause::Action check 100, 150, 200 @@ -186,38 +250,38 @@ check period_seconds: 50, block_ttl: 60, max_allowed: 100 end it 'should define a period check on new instances' do expect(ActionWithCheck.new('id').checks).to eq([ - Pause::PeriodCheck.new(100, 150, 200) - ]) + Pause::PeriodCheck.new(100, 150, 200) + ]) end it 'should define a period check on new instances' do - expect(ActionWithMultipleChecks.new('id').checks).to eq([ - Pause::PeriodCheck.new(100, 150, 200), - Pause::PeriodCheck.new(200, 150, 200), - Pause::PeriodCheck.new(300, 150, 200) - ]) + expect(ActionWithMultipleChecks.new('id').checks).to \ + eq([ + Pause::PeriodCheck.new(100, 150, 200), + Pause::PeriodCheck.new(200, 150, 200), + Pause::PeriodCheck.new(300, 150, 200) + ]) end it 'should accept hash arguments' do expect(ActionWithHashChecks.new('id').checks).to eq([ - Pause::PeriodCheck.new(50, 100, 60) - ]) + Pause::PeriodCheck.new(50, 100, 60) + ]) end - end describe Pause::Action, '.scope' do - class UndefinedScopeAction < Pause::Action + module MyApp + class NoScope < ::Pause::Action + end end it 'should raise if scope is not defined' do - expect { - UndefinedScopeAction.new('1.2.3.4').scope - }.to raise_error('Should implement scope. (Ex: ipn:follow)') + expect(MyApp::NoScope.new('1.2.3.4').scope).to eq 'myapp.noscope' end class DefinedScopeAction < Pause::Action scope 'my:scope' end @@ -234,10 +298,10 @@ end before do Pause.configure do |c| c.resolution = 10 - c.history = 10 + c.history = 10 end end let(:action) { BlockedAction }