All checks were successful
CI / test (pull_request) Successful in 2m40s
Seed plugin `demo-ce-seed` : - Nouveau descriptor dans registry (category visual). - src/lib/plugins/seeds/demo-ce.ts : seedDemoCe() + archiveDemoCe(). Crée org « Comité ESA Kourou (démo) » approved=true + 2 CE_MANAGERs + 3 CE_MEMBERs (password "demo") + 2 carbets co-gérés (OrganizationCarbetMembership) + 1 RentalProvider org-scoped + 4 items (hamac, moustiquaire, kayak, réchaud). - Idempotent : check existence par slug/email avant create. Upsert pour users. - Disable : soft-archive carbets (status=ARCHIVED), delete RentalProvider démo (best-effort si pas de booking), delete users démo (cascade memberships) + delete org. - Branchement hooks onEnable/onDisable dans plugins/hooks.ts. Permet de visualiser le module CE end-to-end sans signup manuel : admin active le plugin → l'org démo et son écosystème apparaissent sur le site (badge « Géré par le CE Comité ESA Kourou (démo) » sur les fiches carbet, items rental dans le catalogue /materiel). ce-invites.ts refactor : - Exporte hashToken (déjà sha256, désormais documenté). - Extrait isInviteValid(row, now=Date) : helper pur testable. La fonction getOrgInviteByToken le réutilise. tests/lib/ce-invites.test.ts (9 cas) : - hashToken : déterminisme, format sha256 64-hex, inputs différents, pas de fuite du plain. - isInviteValid : non consommé+non expiré → vrai ; consommé → faux ; expiré → faux ; les 2 raisons → faux ; injection now pour tests temporels. Total tests : 83/83 ✓ (74 précédents + 9 nouveaux). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
1.7 KiB
TypeScript
52 lines
1.7 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
|
|
vi.mock("server-only", () => ({}));
|
|
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
|
|
|
const { hashToken, isInviteValid } = await import("@/lib/ce-invites");
|
|
|
|
describe("hashToken", () => {
|
|
it("est déterministe — même input → même hash", () => {
|
|
expect(hashToken("abc")).toBe(hashToken("abc"));
|
|
});
|
|
|
|
it("hash sha256 (64 hex chars)", () => {
|
|
expect(hashToken("token")).toMatch(/^[0-9a-f]{64}$/);
|
|
});
|
|
|
|
it("inputs différents → hashes différents", () => {
|
|
expect(hashToken("abc")).not.toBe(hashToken("abd"));
|
|
});
|
|
|
|
it("ne retourne pas le plain (jamais persisté)", () => {
|
|
expect(hashToken("secret-plain-text")).not.toContain("secret");
|
|
});
|
|
});
|
|
|
|
describe("isInviteValid", () => {
|
|
const future = new Date(Date.now() + 24 * 3600 * 1000);
|
|
const past = new Date(Date.now() - 24 * 3600 * 1000);
|
|
|
|
it("vrai si non consommé et non expiré", () => {
|
|
expect(isInviteValid({ expiresAt: future, usedAt: null })).toBe(true);
|
|
});
|
|
|
|
it("faux si déjà consommé", () => {
|
|
expect(isInviteValid({ expiresAt: future, usedAt: new Date() })).toBe(false);
|
|
});
|
|
|
|
it("faux si expiré", () => {
|
|
expect(isInviteValid({ expiresAt: past, usedAt: null })).toBe(false);
|
|
});
|
|
|
|
it("faux si consommé ET expiré (les 2 raisons)", () => {
|
|
expect(isInviteValid({ expiresAt: past, usedAt: new Date() })).toBe(false);
|
|
});
|
|
|
|
it("accepte un `now` injecté pour tests temporels", () => {
|
|
const ref = new Date("2026-06-01");
|
|
const justAfter = new Date("2026-06-02");
|
|
expect(isInviteValid({ expiresAt: justAfter, usedAt: null }, ref)).toBe(true);
|
|
expect(isInviteValid({ expiresAt: ref, usedAt: null }, justAfter)).toBe(false);
|
|
});
|
|
});
|