import { describe, expect, it, vi, beforeEach } from "vitest"; import { appRouter } from "./routers"; import type { TrpcContext } from "./_core/context"; // Mock the db module vi.mock("./db", () => ({ getAllEtablissements: vi.fn().mockResolvedValue([ { id: 1, nom: "Établissement Test", description: null, logoUrl: null, actif: true, createdAt: new Date(), updatedAt: new Date() }, ]), getEtablissementById: vi.fn().mockResolvedValue({ id: 1, nom: "Établissement Test", description: null }), createEtablissement: vi.fn().mockResolvedValue(undefined), updateEtablissement: vi.fn().mockResolvedValue(undefined), deleteEtablissement: vi.fn().mockResolvedValue(undefined), getAllMotsCles: vi.fn().mockResolvedValue([ { id: 1, label: "Bien-être", createdAt: new Date() }, { id: 2, label: "Formation", createdAt: new Date() }, ]), createMotCle: vi.fn().mockResolvedValue(undefined), deleteMotCle: vi.fn().mockResolvedValue(undefined), getPodcasts: vi.fn().mockResolvedValue([ { id: 1, titre: "Podcast Test", resume: "Un résumé de test", etablissementId: 1, etablissementNom: "Établissement Test", audioUrl: "https://example.com/audio.mp3", audioKey: "podcasts/audio/test.mp3", dureeSecondes: 300, statut: "publie", auteurId: 1, auteurNom: "Test User", imageUrl: null, createdAt: new Date(), updatedAt: new Date(), motsCles: [{ id: 1, label: "Bien-être" }], }, ]), getPodcastById: vi.fn().mockResolvedValue({ id: 1, titre: "Podcast Test", resume: "Un résumé de test", etablissementId: 1, etablissementNom: "Établissement Test", audioUrl: "https://example.com/audio.mp3", audioKey: "podcasts/audio/test.mp3", dureeSecondes: 300, statut: "publie", auteurId: 1, auteurNom: "Test User", imageUrl: null, createdAt: new Date(), updatedAt: new Date(), motsCles: [{ id: 1, label: "Bien-être" }], }), createPodcast: vi.fn().mockResolvedValue(42), updatePodcast: vi.fn().mockResolvedValue(undefined), deletePodcast: vi.fn().mockResolvedValue(undefined), getPodcastStats: vi.fn().mockResolvedValue({ total: 5, publies: 3, brouillons: 2, etablissements: 2 }), getAllUsers: vi.fn().mockResolvedValue([]), updateUserRole: vi.fn().mockResolvedValue(undefined), upsertUser: vi.fn().mockResolvedValue(undefined), getUserByOpenId: vi.fn().mockResolvedValue(undefined), })); function makePublicCtx(): TrpcContext { return { user: null, req: { protocol: "https", headers: {} } as TrpcContext["req"], res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"], }; } function makeUserCtx(role: "user" | "admin" = "user"): TrpcContext { return { user: { id: 1, openId: "test-user", name: "Test User", email: "test@example.com", loginMethod: "email", role, createdAt: new Date(), updatedAt: new Date(), lastSignedIn: new Date(), }, req: { protocol: "https", headers: {} } as TrpcContext["req"], res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"], }; } // ─── Établissements ──────────────────────────────────────────────────────────── describe("etablissements.list", () => { it("retourne la liste des établissements sans authentification", async () => { const caller = appRouter.createCaller(makePublicCtx()); const result = await caller.etablissements.list(); expect(Array.isArray(result)).toBe(true); expect(result.length).toBeGreaterThan(0); expect(result[0]).toHaveProperty("nom"); }); }); describe("etablissements.create", () => { it("refuse la création sans authentification admin", async () => { const caller = appRouter.createCaller(makePublicCtx()); await expect( caller.etablissements.create({ nom: "Test" }) ).rejects.toThrow(); }); it("refuse la création pour un utilisateur standard", async () => { const caller = appRouter.createCaller(makeUserCtx("user")); await expect( caller.etablissements.create({ nom: "Test" }) ).rejects.toMatchObject({ code: "FORBIDDEN" }); }); it("permet la création pour un administrateur", async () => { const caller = appRouter.createCaller(makeUserCtx("admin")); await expect( caller.etablissements.create({ nom: "Nouvel Établissement" }) ).resolves.not.toThrow(); }); }); // ─── Mots-clés ───────────────────────────────────────────────────────────────── describe("motsCles.list", () => { it("retourne la liste des mots-clés publiquement", async () => { const caller = appRouter.createCaller(makePublicCtx()); const result = await caller.motsCles.list(); expect(Array.isArray(result)).toBe(true); expect(result.length).toBe(2); expect(result[0]).toHaveProperty("label"); }); }); describe("motsCles.create", () => { it("refuse la création sans rôle admin", async () => { const caller = appRouter.createCaller(makeUserCtx("user")); await expect( caller.motsCles.create({ label: "Test" }) ).rejects.toMatchObject({ code: "FORBIDDEN" }); }); }); // ─── Podcasts ────────────────────────────────────────────────────────────────── describe("podcasts.list", () => { it("retourne la liste des podcasts publiquement", async () => { const caller = appRouter.createCaller(makePublicCtx()); const result = await caller.podcasts.list({}); expect(Array.isArray(result)).toBe(true); expect(result[0]).toHaveProperty("titre"); expect(result[0]).toHaveProperty("motsCles"); }); it("accepte un filtre par établissement", async () => { const caller = appRouter.createCaller(makePublicCtx()); const result = await caller.podcasts.list({ etablissementId: 1 }); expect(Array.isArray(result)).toBe(true); }); it("accepte un filtre par statut", async () => { const caller = appRouter.createCaller(makePublicCtx()); const result = await caller.podcasts.list({ statut: "publie" }); expect(Array.isArray(result)).toBe(true); }); }); describe("podcasts.getById", () => { it("retourne un podcast par son id", async () => { const caller = appRouter.createCaller(makePublicCtx()); const result = await caller.podcasts.getById({ id: 1 }); expect(result).toHaveProperty("id", 1); expect(result).toHaveProperty("titre", "Podcast Test"); }); }); describe("podcasts.create", () => { it("refuse la création sans authentification", async () => { const caller = appRouter.createCaller(makePublicCtx()); await expect( caller.podcasts.create({ titre: "Test", resume: "Résumé", etablissementId: 1, statut: "brouillon", motCleIds: [], }) ).rejects.toThrow(); }); it("permet la création pour un utilisateur authentifié", async () => { const caller = appRouter.createCaller(makeUserCtx("user")); const result = await caller.podcasts.create({ titre: "Nouveau Podcast", resume: "Un résumé complet", etablissementId: 1, statut: "brouillon", motCleIds: [1], }); expect(result).toBe(42); }); }); describe("podcasts.delete", () => { it("refuse la suppression pour un utilisateur non propriétaire", async () => { const caller = appRouter.createCaller(makeUserCtx("user")); // auteurId est 1, user.id est 1 → devrait réussir await expect( caller.podcasts.delete({ id: 1 }) ).resolves.not.toThrow(); }); }); describe("podcasts.stats", () => { it("retourne les statistiques pour un utilisateur authentifié", async () => { const caller = appRouter.createCaller(makeUserCtx("user")); const result = await caller.podcasts.stats(); expect(result).toHaveProperty("total"); expect(result).toHaveProperty("publies"); expect(result).toHaveProperty("brouillons"); expect(result).toHaveProperty("etablissements"); }); }); // ─── Auth ────────────────────────────────────────────────────────────────────── describe("auth.me", () => { it("retourne null pour un utilisateur non connecté", async () => { const caller = appRouter.createCaller(makePublicCtx()); const result = await caller.auth.me(); expect(result).toBeNull(); }); it("retourne l'utilisateur connecté", async () => { const caller = appRouter.createCaller(makeUserCtx("user")); const result = await caller.auth.me(); expect(result).toHaveProperty("id", 1); expect(result).toHaveProperty("role", "user"); }); });