require 'spec_helper'

describe Ratelimit do
  describe '.initialize' do
    subject { described_class.new(key, options) }

    let(:options) { Hash.new }

    context 'with key' do
      let(:key) { 'key' }

      context 'with redis option' do
        let(:redis) { double('redis') }
        let(:options) { super().merge(redis: redis) }

        it 'wraps redis in redis-namespace' do
          expect(redis).to receive(:script).with(:load, anything).twice
          expect(subject.send(:redis)).to be_instance_of(Redis::Namespace)
        end
      end
    end
  end

  before do
    @r = Ratelimit.new("key")
    @r.send(:redis).flushdb
  end

  it "should set_bucket_expiry to the bucket_span if not defined" do
    expect(@r.instance_variable_get(:@bucket_span)).to eq(@r.instance_variable_get(:@bucket_expiry))
  end

  it "should not allow bucket count less than 3" do
    expect do
      Ratelimit.new("key", {:bucket_span => 1, :bucket_interval => 1})
    end.to raise_error(ArgumentError)
  end

  it "should not allow bucket expiry to be larger than the bucket span" do
    expect do
      Ratelimit.new("key", {:bucket_expiry => 1200})
    end.to raise_error(ArgumentError)
  end

  it "should not allow redis to be passed outside of the options hash" do
    expect do
      Ratelimit.new("key", Redis.new)
    end.to raise_error(ArgumentError)
  end

  it "should be able to add to the count for a given subject" do
    @r.add("value1")
    @r.add("value1")
    expect(@r.count("value1", 1)).to eq(2)
    expect(@r.count("value2", 1)).to eq(0)
    Timecop.travel(600) do
      expect(@r.count("value1", 1)).to eq(0)
    end
  end

  it "should be able to add to the count by more than 1" do
    @r.add("value1", 3)
    expect(@r.count("value1", 1)).to eq(3)
  end

  it "should be able to add to the count for a non-string subject" do
    @r.add(123)
    @r.add(123)
    expect(@r.count(123, 1)).to eq(2)
    expect(@r.count(124, 1)).to eq(0)
    Timecop.travel(10) do
      expect(@r.count(123, 1)).to eq(0)
    end
  end

  it "should return counter value" do
    counter_value = @r.add("value1")
    expect(@r.count("value1", 1)).to eq(counter_value)
  end

  it "respond to exceeded? method correctly" do
    5.times do
      @r.add("value1")
    end

    expect(@r.exceeded?("value1", {:threshold => 10, :interval => 30})).to be false
    expect(@r.within_bounds?("value1", {:threshold => 10, :interval => 30})).to be true

    10.times do
      @r.add("value1")
    end

    expect(@r.exceeded?("value1", {:threshold => 10, :interval => 30})).to be true
    expect(@r.within_bounds?("value1", {:threshold => 10, :interval => 30})).to be false
  end

  it "accept a threshold and a block that gets executed once it's below the threshold" do
    expect(@r.count("key", 30)).to eq(0)
    31.times do
      @r.add("key")
    end
    expect(@r.count("key", 30)).to eq(31)

    @value = nil
    expect do
      timeout(1) do
        @r.exec_within_threshold("key", {:threshold => 30, :interval => 30}) do
          @value = 2
        end
      end
    end.to raise_error(Timeout::Error)
    expect(@value).to be nil
    Timecop.travel(40) do
      @r.exec_within_threshold("key", {:threshold => 30, :interval => 30}) do
        @value = 1
      end
    end
    expect(@value).to be 1
  end

  it "counts correctly if bucket_span equals count-interval  " do
    @r = Ratelimit.new("key", {:bucket_span => 10, bucket_interval: 1})
    @r.add('value1')
    expect(@r.count('value1', 10)).to eql(1)
  end

  it "counts correctly if interval is greater than bucket_span" do
    @r = Ratelimit.new("key", { bucket_span: 10, bucket_interval: 1})
    @r.add('value1')
    expect(@r.count('value1', 40)).to eql(1)
  end
end
