require 'spec_helper' require 'date' require 'timecop' describe Pause::Redis::Adapter do let(:resolution) { 10 } let(:history) { 60 } let(:configuration) { Pause::Configuration.new } before do allow(Pause).to receive(:config).and_return(configuration) allow(Pause.config).to receive(:resolution).and_return(resolution) allow(Pause.config).to receive(:history).and_return(history) redis_conn.flushall end let(:adapter) { Pause::Redis::Adapter.new(Pause.config) } let(:redis_conn) { adapter.send(:redis) } describe '#increment' do let(:scope) { 'blah' } let(:identifier) { '213213' } let(:tracked_key) { 'i:blah:|213213|' } it 'should add key to a redis set' do adapter.increment(scope, identifier, Time.now.to_i) set = redis_conn.zrange(tracked_key, 0, -1, :with_scores => true) expect(set).to_not be_empty expect(set.size).to eql(1) expect(set[0].size).to eql(2) end it 'should remove old key from a redis set' do time = Time.now expect(redis_conn).to receive(:zrem).with(tracked_key, [adapter.period_marker(resolution, time)]) adapter.time_blocks_to_keep = 1 Timecop.freeze time do adapter.increment(scope, identifier, Time.now.to_i) end Timecop.freeze time + (adapter.resolution + 1) do adapter.increment(scope, identifier, Time.now.to_i) end end it 'sets expiry on key' do expect(redis_conn).to receive(:expire).with(tracked_key, history) adapter.increment(scope, identifier, Time.now.to_i) end end describe '#expire_block_list' do let(:scope) { 'a' } let(:expired_identifier) { '123' } let(:blocked_identifier) { '124' } it 'clears all entries with score older than now' do now = Time.now Timecop.freeze now - 10 do adapter.rate_limit!(scope, expired_identifier, 5) end Timecop.freeze now - 4 do adapter.rate_limit!(scope, blocked_identifier, 5) end adapter.expire_block_list(scope) expect(redis_conn.zscore('b:|a|', blocked_identifier)).not_to be nil expect(redis_conn.zscore('b:|a|', expired_identifier)).to be nil end end describe '#rate_limit!' do end describe '#rate_limited?' do let(:scope) { 'ipn:follow' } let(:identifier) { '123461234' } let(:blocked_key) { "b:#{key}" } let(:ttl) { 110000 } it 'should return true if blocked' do adapter.rate_limit!(scope, identifier, ttl) expect(adapter.rate_limited?(scope, identifier)).to be true end end describe '#tracked_key' do it 'prefixes key' do expect(adapter.send(:tracked_key, 'abc', '12345')).to eq('i:abc:|12345|') end end describe '#enable' do it 'deletes the disabled flag in redis' do adapter.disable('boom') expect(adapter.disabled?('boom')).to be true adapter.enable('boom') expect(adapter.disabled?('boom')).to be false end end describe '#disable' do it 'sets the disabled flag in redis' do expect(adapter.enabled?('boom')).to be true adapter.disable('boom') expect(adapter.enabled?('boom')).to be false end end describe '#rate_limit!' do it 'rate limits a key for a specific ttl' do expect(adapter.rate_limited?('blah', '1')).to be false adapter.rate_limit!('blah', '1', 10) expect(adapter.rate_limited?('blah', '1')).to be true end describe 'redis internals' do let(:scope) { 'ipn:follow' } let(:identifier) { '1234' } let(:blocked_key) { "b:|#{scope}|" } let(:ttl) { 110000 } it 'saves ip to redis with expiration' do time = Time.now Timecop.freeze time do adapter.rate_limit!(scope, identifier, ttl) end expect(redis_conn.zscore(blocked_key, identifier)).to_not be nil expect(redis_conn.zscore(blocked_key, identifier)).to eq(time.to_i + ttl) end end end describe '#delete_rate_limited_keys' do context 'with blocked items' do before do adapter.rate_limit!('boom', '1', 10) adapter.rate_limit!('boom', '2', 10) expect(adapter.rate_limited?('boom', '1')).to be true expect(adapter.rate_limited?('boom', '2')).to be true end it 'calls redis del with all keys' do adapter.delete_rate_limited_keys('boom') expect(adapter.rate_limited?('boom', '1')).to be false expect(adapter.rate_limited?('boom', '2')).to be false end it 'returns the number of unblocked items' do expect(adapter.delete_rate_limited_keys('boom')).to eq(2) end end context 'with no blocked items' do it 'returns 0' do expect(adapter.delete_rate_limited_keys('boom')).to eq(0) end end end describe '#delete_rate_limit_key' do it 'calls redis del with all keys' do adapter.rate_limit!('boom', '1', 10) adapter.rate_limit!('boom', '2', 10) expect(adapter.rate_limited?('boom', '1')).to be true expect(adapter.rate_limited?('boom', '2')).to be true adapter.delete_rate_limited_key('boom', '1') expect(adapter.rate_limited?('boom', '1')).to be false expect(adapter.rate_limited?('boom', '2')).to be true end end end