import { describe, expect, it } from "vitest"; import { RateLimiter } from "../src/auth/rate-limit"; const T0 = new Date("2026-01-01T00:00:00Z"); const at = (ms: number) => new Date(T0.getTime() + ms); describe("RateLimiter (fenêtre glissante)", () => { it("autorise jusqu'à maxHits puis bloque", () => { const rl = new RateLimiter(3, 60_000); expect(rl.hit("a", T0)).toBe(true); expect(rl.hit("a", at(1000))).toBe(true); expect(rl.hit("a", at(2000))).toBe(true); expect(rl.hit("a", at(3000))).toBe(false); }); it("les hits anciens sortent de la fenêtre", () => { const rl = new RateLimiter(2, 60_000); expect(rl.hit("a", T0)).toBe(true); expect(rl.hit("a", at(30_000))).toBe(true); expect(rl.hit("a", at(31_000))).toBe(false); // Le hit de T0 a expiré de la fenêtre (60 s). expect(rl.hit("a", at(61_001))).toBe(true); }); it("reset vide la clé", () => { const rl = new RateLimiter(1, 60_000); expect(rl.hit("a", T0)).toBe(true); expect(rl.hit("a", at(1000))).toBe(false); rl.reset("a"); expect(rl.hit("a", at(2000))).toBe(true); }); it("remaining reflète les tentatives restantes", () => { const rl = new RateLimiter(2, 60_000); expect(rl.remaining("a", T0)).toBe(2); rl.hit("a", T0); expect(rl.remaining("a", at(1000))).toBe(1); }); it("les clés sont isolées", () => { const rl = new RateLimiter(1, 60_000); expect(rl.hit("a", T0)).toBe(true); expect(rl.hit("b", T0)).toBe(true); }); });