require 'test_helper'

class RateLimitTest < ActiveSupport::TestCase
  context "RateLimit: " do
    context "#limit! method" do
      should "create a new rate limit object if none exists, or update it if it already exists" do
        assert_difference("RateLimit.count", 1) do
          RateLimiter.new("write", ["users/1"]).limited?
        end

        assert_difference("RateLimit.count", 0) do
          RateLimiter.new("write", ["users/1"]).limited?
        end

        assert_difference("RateLimit.count", 1) do
          RateLimiter.new("write", ["users/1", "ip/1.2.3.4"]).limited?
        end

        assert_difference("RateLimit.count", 0) do
          RateLimiter.new("write", ["users/1", "ip/1.2.3.4"]).limited?
        end
      end

      should "include the cost of the first action when initializing the limit" do
        limiter = RateLimiter.new("write", ["users/1"], burst: 10, cost: 1)
        assert_equal(9, limiter.rate_limits.first.points)
      end

      should "be limited and penalize the caller 1 second if the point count is negative" do
        freeze_time
        create(:rate_limit, action: "write", key: "users/1", points: -1)
        limiter = RateLimiter.new("write", ["users/1"], cost: 1)

        assert_equal(true, limiter.limited?)
        assert_equal(-2, limiter.rate_limits.first.points)
      end

      should "not be limited if the point count was positive before the action" do
        freeze_time
        create(:rate_limit, action: "write", key: "users/1", points: 0.01)
        limiter = RateLimiter.new("write", ["users/1"], cost: 1)

        assert_equal(false, limiter.limited?)
        assert_equal(-0.99, limiter.rate_limits.first.points)
      end

      should "refill the points at the correct rate" do
        freeze_time
        create(:rate_limit, action: "write", key: "users/1", points: -2)

        limiter = RateLimiter.new("write", ["users/1"], cost: 1, rate: 1, burst: 10)
        assert_equal(true, limiter.limited?)
        assert_equal(-3, limiter.rate_limits.first.points)

        travel 1.second
        limiter = RateLimiter.new("write", ["users/1"], cost: 1, rate: 1, burst: 10)
        assert_equal(true, limiter.limited?)
        assert_equal(-3, limiter.rate_limits.first.points)

        travel 5.second
        limiter = RateLimiter.new("write", ["users/1"], cost: 1, rate: 1, burst: 10)
        assert_equal(false, limiter.limited?)
        assert_equal(1, limiter.rate_limits.first.points)

        travel 60.second
        limiter = RateLimiter.new("write", ["users/1"], cost: 1, rate: 1, burst: 10)
        assert_equal(false, limiter.limited?)
        assert_equal(9, limiter.rate_limits.first.points)
      end
    end
  end
end
