import { describe, expect, it, vi, afterEach } from "vitest"; import { checkRateLimit } from "../rate-limit"; describe("checkRateLimit", () => { afterEach(() => { vi.useRealTimers(); }); it("allows requests under the limit", () => { const key = `test:${crypto.randomUUID()}`; for (let i = 0; i < 3; i++) { const result = checkRateLimit(key, { maxAttempts: 3, windowMs: 60_000 }); expect(result.allowed).toBe(true); } }); it("blocks the request once the limit is exceeded within the window", () => { const key = `test:${crypto.randomUUID()}`; for (let i = 0; i < 3; i++) { checkRateLimit(key, { maxAttempts: 3, windowMs: 60_000 }); } const blocked = checkRateLimit(key, { maxAttempts: 3, windowMs: 60_000 }); expect(blocked.allowed).toBe(false); expect(blocked.retryAfterSeconds).toBeGreaterThan(0); }); it("tracks each key independently", () => { const keyA = `test:${crypto.randomUUID()}`; const keyB = `test:${crypto.randomUUID()}`; for (let i = 0; i < 3; i++) { checkRateLimit(keyA, { maxAttempts: 3, windowMs: 60_000 }); } // keyA is now exhausted, but keyB has never been touched. expect(checkRateLimit(keyA, { maxAttempts: 3, windowMs: 60_000 }).allowed).toBe(false); expect(checkRateLimit(keyB, { maxAttempts: 3, windowMs: 60_000 }).allowed).toBe(true); }); it("resets once the window elapses", () => { vi.useFakeTimers(); const key = `test:${crypto.randomUUID()}`; for (let i = 0; i < 2; i++) { checkRateLimit(key, { maxAttempts: 2, windowMs: 1_000 }); } expect(checkRateLimit(key, { maxAttempts: 2, windowMs: 1_000 }).allowed).toBe(false); vi.advanceTimersByTime(1_001); expect(checkRateLimit(key, { maxAttempts: 2, windowMs: 1_000 }).allowed).toBe(true); }); });