import { describe, expect, it } from "vitest"; import { createMemoryRateLimiter, createDedupe, clientIp, fingerprint, contentSecurityPolicy, } from "@/lib/security"; describe("limitation de débit", () => { it("bloque après N requêtes dans la fenêtre puis libère", () => { const rl = createMemoryRateLimiter(3, 60); const t0 = 1_000_000; expect(rl.check("a", t0).allowed).toBe(true); expect(rl.check("a", t0 + 1).allowed).toBe(true); expect(rl.check("a", t0 + 2).allowed).toBe(true); const blocked = rl.check("a", t0 + 3); expect(blocked.allowed).toBe(false); expect(blocked.retryAfterS).toBeGreaterThan(0); expect(rl.check("b", t0 + 3).allowed).toBe(true); expect(rl.check("a", t0 + 61_000).allowed).toBe(true); }); }); describe("dédoublonnage", () => { it("signale un doublon dans la fenêtre seulement", () => { const d = createDedupe(10); expect(d.isDuplicate("k", 0)).toBe(false); expect(d.isDuplicate("k", 5_000)).toBe(true); expect(d.isDuplicate("k", 20_000)).toBe(false); }); }); describe("utilitaires", () => { it("lit la première IP de X-Forwarded-For", () => { expect(clientIp(new Headers({ "x-forwarded-for": "203.0.113.5, 10.0.0.1" }))).toBe( "203.0.113.5", ); expect(clientIp(new Headers())).toBe("unknown"); }); it("produit une empreinte stable non réversible", () => { expect(fingerprint("a")).toBe(fingerprint("a")); expect(fingerprint("a")).not.toBe(fingerprint("b")); expect(fingerprint("a")).toHaveLength(12); }); it("la CSP interdit l'intégration dans une iframe et les objets", () => { const csp = contentSecurityPolicy(); expect(csp).toContain("frame-ancestors 'none'"); expect(csp).toContain("object-src 'none'"); }); });