import { describe, expect, it, vi, beforeEach } from "vitest"; import { appRouter } from "./routers"; import type { TrpcContext } from "./_core/context"; import bcrypt from "bcryptjs"; // ─── Mocks ───────────────────────────────────────────────────────────────────── const mockPasswordHash = bcrypt.hashSync("Itinova69!", 10); vi.mock("./db", () => ({ getUserByUsername: vi.fn().mockImplementation(async (username: string) => { if (username === "adminServPodcast") { return { id: 99, openId: "local-adminServPodcast", username: "adminServPodcast", name: "Administrateur Service Podcast", email: null, loginMethod: "local", role: "admin", passwordHash: mockPasswordHash, immutable: true, createdAt: new Date(), updatedAt: new Date(), lastSignedIn: new Date(), }; } return undefined; }), getUserByOpenId: vi.fn().mockImplementation(async (openId: string) => { if (openId === "local-adminServPodcast") { return { id: 99, openId: "local-adminServPodcast", username: "adminServPodcast", name: "Administrateur Service Podcast", email: null, loginMethod: "local", role: "admin", passwordHash: mockPasswordHash, immutable: true, createdAt: new Date(), updatedAt: new Date(), lastSignedIn: new Date(), }; } return undefined; }), upsertUser: vi.fn().mockResolvedValue(undefined), getAllEtablissements: vi.fn().mockResolvedValue([]), getAllMotsCles: vi.fn().mockResolvedValue([]), getPodcasts: vi.fn().mockResolvedValue([]), getPodcastById: vi.fn().mockResolvedValue(null), createPodcast: vi.fn().mockResolvedValue(1), updatePodcast: vi.fn().mockResolvedValue(undefined), deletePodcast: vi.fn().mockResolvedValue(undefined), getPodcastStats: vi.fn().mockResolvedValue({ total: 0, publies: 0, brouillons: 0, etablissements: 0 }), getAllUsers: vi.fn().mockResolvedValue([]), updateUserRole: vi.fn().mockResolvedValue(undefined), createLocalUser: vi.fn().mockResolvedValue(undefined), getEtablissementById: vi.fn().mockResolvedValue(null), createEtablissement: vi.fn().mockResolvedValue(undefined), updateEtablissement: vi.fn().mockResolvedValue(undefined), deleteEtablissement: vi.fn().mockResolvedValue(undefined), createMotCle: vi.fn().mockResolvedValue(undefined), deleteMotCle: vi.fn().mockResolvedValue(undefined), })); // ─── Helpers ─────────────────────────────────────────────────────────────────── function makePublicCtx(): TrpcContext { const cookies: Record = {}; return { user: null, req: { protocol: "https", headers: {}, } as TrpcContext["req"], res: { clearCookie: vi.fn(), cookie: vi.fn((name: string, value: string) => { cookies[name] = value; }), } as unknown as TrpcContext["res"], }; } // ─── Tests ───────────────────────────────────────────────────────────────────── describe("auth.loginLocal", () => { it("réussit avec les bonnes credentials adminServPodcast", async () => { const ctx = makePublicCtx(); const caller = appRouter.createCaller(ctx); const result = await caller.auth.loginLocal({ username: "adminServPodcast", password: "Itinova69!", }); expect(result.success).toBe(true); expect(result.user).toBeDefined(); expect(result.user.username).toBe("adminServPodcast"); expect(result.user.role).toBe("admin"); expect(result.user.immutable).toBe(true); }); it("échoue avec un mauvais mot de passe", async () => { const ctx = makePublicCtx(); const caller = appRouter.createCaller(ctx); await expect( caller.auth.loginLocal({ username: "adminServPodcast", password: "mauvais-mot-de-passe", }) ).rejects.toMatchObject({ code: "UNAUTHORIZED", message: "Identifiant ou mot de passe incorrect", }); }); it("échoue avec un utilisateur inexistant", async () => { const ctx = makePublicCtx(); const caller = appRouter.createCaller(ctx); await expect( caller.auth.loginLocal({ username: "utilisateurInexistant", password: "n'importe-quoi", }) ).rejects.toMatchObject({ code: "UNAUTHORIZED", }); }); it("définit un cookie de session après connexion réussie", async () => { const setCookieCalls: Array<{ name: string; value: string }> = []; const ctx: TrpcContext = { user: null, req: { protocol: "https", headers: {} } as TrpcContext["req"], res: { clearCookie: vi.fn(), cookie: vi.fn((name: string, value: string) => { setCookieCalls.push({ name, value }); }), } as unknown as TrpcContext["res"], }; const caller = appRouter.createCaller(ctx); await caller.auth.loginLocal({ username: "adminServPodcast", password: "Itinova69!", }); expect(setCookieCalls.length).toBeGreaterThan(0); expect(setCookieCalls[0].name).toBeDefined(); expect(typeof setCookieCalls[0].value).toBe("string"); expect(setCookieCalls[0].value.length).toBeGreaterThan(10); }); }); describe("compte adminServPodcast - propriétés immuables", () => { it("le compte est marqué immutable = true", async () => { const ctx = makePublicCtx(); const caller = appRouter.createCaller(ctx); const result = await caller.auth.loginLocal({ username: "adminServPodcast", password: "Itinova69!", }); expect(result.user.immutable).toBe(true); }); it("le compte a le rôle admin", async () => { const ctx = makePublicCtx(); const caller = appRouter.createCaller(ctx); const result = await caller.auth.loginLocal({ username: "adminServPodcast", password: "Itinova69!", }); expect(result.user.role).toBe("admin"); }); it("le compte utilise la méthode de connexion locale", async () => { const ctx = makePublicCtx(); const caller = appRouter.createCaller(ctx); const result = await caller.auth.loginLocal({ username: "adminServPodcast", password: "Itinova69!", }); expect(result.user.loginMethod).toBe("local"); }); });