tarakan/test/tarakan_web/plugs/api_rate_limit_test.exs
Maxfield Luke af6077b9c3
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s
Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
2026-07-29 04:43:40 -04:00

85 lines
2.5 KiB
Elixir
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

defmodule TarakanWeb.Plugs.ApiRateLimitTest do
use Tarakan.DataCase, async: true
import Plug.Conn
import Plug.Test
alias Tarakan.Accounts.Scope
alias TarakanWeb.Plugs.ApiRateLimit
defp call_ip(conn, opts) do
ApiRateLimit.call(conn, ApiRateLimit.init([mode: :ip] ++ opts))
end
defp call_actor(conn, scope, opts) do
conn
|> assign(:current_scope, scope)
|> ApiRateLimit.call(ApiRateLimit.init([mode: :actor] ++ opts))
end
defp credential_scope(account, token_id) do
Scope.for_account(account, token_id: token_id, authentication_method: :api_credential)
end
test "requests carrying a Bearer token count against the IP bucket" do
opts = [request_limit: 2, mutation_limit: 2, window_seconds: 60]
for _ <- 1..2 do
conn =
conn(:get, "/api/repositories")
|> put_req_header("authorization", "Bearer garbage-token")
|> call_ip(opts)
refute conn.halted
end
conn =
conn(:get, "/api/repositories")
|> put_req_header("authorization", "Bearer garbage-token")
|> call_ip(opts)
assert conn.halted
assert conn.status == 429
assert Jason.decode!(conn.resp_body)["error"] == "rate limit exceeded"
end
test "IPv6 clients in the same /64 share one IP bucket" do
opts = [request_limit: 1, mutation_limit: 1, window_seconds: 60]
first =
conn(:get, "/api/repositories")
|> Map.put(:remote_ip, {0x2001, 0x0DB8, 0, 0, 0, 0, 0, 1})
|> call_ip(opts)
refute first.halted
second =
conn(:get, "/api/repositories")
|> Map.put(:remote_ip, {0x2001, 0x0DB8, 0, 0, 0xAAAA, 0xBBBB, 0xCCCC, 0xDDDD})
|> call_ip(opts)
assert second.halted
assert second.status == 429
end
test "plain requests still count against the IP bucket" do
opts = [request_limit: 1, mutation_limit: 1, window_seconds: 60]
refute conn(:get, "/api/repositories") |> call_ip(opts) |> Map.fetch!(:halted)
assert conn(:get, "/api/repositories") |> call_ip(opts) |> Map.fetch!(:halted)
end
test "authenticated actors get the multiplied actor budget" do
opts = [request_limit: 2, mutation_limit: 2, window_seconds: 60]
scope = credential_scope(account_fixture(), 11_001)
# 2 × @actor_multiplier (4) = 8 requests fit; the 9th is rejected.
for _ <- 1..8 do
refute conn(:get, "/api/jobs") |> call_actor(scope, opts) |> Map.fetch!(:halted)
end
limited = conn(:get, "/api/jobs") |> call_actor(scope, opts)
assert limited.halted
assert limited.status == 429
end
end