import { describe, expect, test } from "bun:test"; import config from "../src/config.ts"; import { checkRateLimit, getClientIp } from "../src/lib/rateLimiter.ts"; describe("checkRateLimit", () => { test("allows up to the limit, then blocks", () => { const ip = `1.1.1.${Math.random()}`; for (let i = 0; i < 3; i++) { expect(checkRateLimit(ip, "login", 3, 60_000)).toBe(true); } expect(checkRateLimit(ip, "login", 3, 60_000)).toBe(false); }); test("buckets are per kind", () => { const ip = `2.2.2.${Math.random()}`; expect(checkRateLimit(ip, "login", 1, 60_000)).toBe(true); expect(checkRateLimit(ip, "login", 1, 60_000)).toBe(false); expect(checkRateLimit(ip, "comment", 1, 60_000)).toBe(true); }); test("buckets are per key", () => { const kind = "reaction" as const; expect(checkRateLimit("3.3.3.1", kind, 1, 60_000)).toBe(true); expect(checkRateLimit("3.3.3.1", kind, 1, 60_000)).toBe(false); expect(checkRateLimit("3.3.3.2", kind, 1, 60_000)).toBe(true); }); test("the window resets", async () => { const ip = `4.4.4.${Math.random()}`; expect(checkRateLimit(ip, "upload", 1, 20)).toBe(true); expect(checkRateLimit(ip, "upload", 1, 20)).toBe(false); await Bun.sleep(40); expect(checkRateLimit(ip, "upload", 1, 20)).toBe(true); }); test("counting stays correct past the old 1000-call sweep threshold", () => { const ip = `5.5.5.${Math.random()}`; for (let i = 0; i < 1200; i++) { checkRateLimit(`${ip}-${i}`, "register", 5, 60_000); } for (let i = 0; i < 5; i++) { expect(checkRateLimit(ip, "register", 5, 60_000)).toBe(true); } expect(checkRateLimit(ip, "register", 5, 60_000)).toBe(false); }); test("a null key is always allowed", () => { expect(checkRateLimit(null, "login", 0, 60_000)).toBe(true); }); test("RATE_LIMIT_DISABLED short-circuits", () => { const saved = config.RATE_LIMIT_DISABLED; config.RATE_LIMIT_DISABLED = true; try { const ip = `6.6.6.${Math.random()}`; for (let i = 0; i < 10; i++) { expect(checkRateLimit(ip, "login", 1, 60_000)).toBe(true); } } finally { config.RATE_LIMIT_DISABLED = saved; } }); }); describe("getClientIp", () => { test("uses X-Forwarded-For only when TRUSTED_PROXY is set", () => { const req = new Request("http://x/", { headers: { "x-forwarded-for": "9.9.9.9, 10.0.0.1" }, }); const saved = config.TRUSTED_PROXY; try { config.TRUSTED_PROXY = true; expect(getClientIp(req, null)).toBe("9.9.9.9"); config.TRUSTED_PROXY = false; expect(getClientIp(req, null)).toBeNull(); } finally { config.TRUSTED_PROXY = saved; } }); });