1.8 KiB · text History b868907
defmodule GitGud.RateLimitTest do
  use ExUnit.Case, async: true

  alias GitGud.RateLimit

  # Unique keys per test, since the table is shared with the whole suite.
  defp key, do: "k-#{System.unique_integer([:positive])}"

  test "check counts nothing; record counts; the limit refuses" do
    k = key()
    {limit, _window} = RateLimit.limits(:login_account)

    for _ <- 1..(limit - 1), do: RateLimit.record(:login_account, k)
    assert RateLimit.check(:login_account, k) == :ok
    assert RateLimit.check(:login_account, k) == :ok

    RateLimit.record(:login_account, k)
    assert {:error, retry_after} = RateLimit.check(:login_account, k)
    assert retry_after in 1..900
  end

  test "hit lets the limit through and refuses the one after" do
    k = key()
    {limit, _window} = RateLimit.limits(:email_address)

    for _ <- 1..limit, do: assert(RateLimit.hit(:email_address, k) == :ok)
    assert {:error, _} = RateLimit.hit(:email_address, k)
  end

  test "keys and policies are counted apart, and clear forgets" do
    k = key()
    {limit, _window} = RateLimit.limits(:login_account)

    for _ <- 1..limit, do: RateLimit.record(:login_account, k)
    assert {:error, _} = RateLimit.check(:login_account, k)
    assert RateLimit.check(:login_account, key()) == :ok
    assert RateLimit.check(:login_ip, k) == :ok

    RateLimit.clear(:login_account, k)
    assert RateLimit.check(:login_account, k) == :ok
  end

  test "minutes rounds up" do
    assert RateLimit.minutes(1) == 1
    assert RateLimit.minutes(60) == 1
    assert RateLimit.minutes(61) == 2
  end

  test "ip_key is the address, or the /64 for IPv6" do
    assert RateLimit.ip_key({203, 0, 113, 7}) == "203.0.113.7"
    assert RateLimit.ip_key({0x2001, 0xDB8, 1, 2, 3, 4, 5, 6}) == "2001:db8:1:2::/64"
    assert RateLimit.ip_key(nil) == "unknown"
  end
end