import { describe, it, expect, vi, beforeEach } from "vitest"; import { Hono } from "hono"; // ─── Mocks ─────────────────────────────────────────────────────────────── vi.mock("../../middleware/auth", () => ({ authMiddleware: async (c: any, next: any) => { const authHeader = c.req.header("Authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) { return c.json({ error: true, code: "AUTH_REQUIRED" }, 401); } c.set("profile", { id: "user-1", email: "u@test.com", plan: "standard", simulations_used: 3, }); await next(); }, })); const { correctEOMock } = vi.hoisted(() => ({ correctEOMock: vi.fn() })); vi.mock("../../controllers/correctionController", () => ({ correctEE: vi.fn(), correctEO: correctEOMock, })); import correctionsRoutes from "../corrections"; function buildApp() { const app = new Hono(); app.route("/corrections", correctionsRoutes); return app; } const AUTH = { Authorization: "Bearer x" }; const JSON_HEADERS = { ...AUTH, "Content-Type": "application/json" }; describe("POST /corrections/eo — Sprint 4a", () => { beforeEach(() => { correctEOMock.mockReset(); }); it("401 sans Authorization", async () => { const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST" }); expect(res.status).toBe(401); }); it("400 si simulationId manquant", async () => { const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ tache: "EO_T1", transcript: "t" }), }); expect(res.status).toBe(400); const body = await res.json(); expect(body.code).toBe("VALIDATION_ERROR"); }); it("400 si tache invalide (EO_T2 par exemple)", async () => { const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s1", tache: "EO_T2", transcript: "t", }), }); expect(res.status).toBe(400); }); it("400 si ni transcript ni audioBase64 fournis", async () => { const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s1", tache: "EO_T1" }), }); expect(res.status).toBe(400); const body = await res.json(); expect(body.code).toBe("VALIDATION_ERROR"); }); it("400 si transcript ET audioBase64 fournis simultanément (XOR)", async () => { const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s1", tache: "EO_T1", transcript: "t", audioBase64: "AAAA", mimeType: "audio/webm", }), }); expect(res.status).toBe(400); }); it("400 si audioBase64 sans mimeType", async () => { const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s1", tache: "EO_T1", audioBase64: "AAAA", }), }); expect(res.status).toBe(400); }); it("400 si nclc_cible invalide", async () => { const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s1", tache: "EO_T1", transcript: "t", nclc_cible: 8, }), }); expect(res.status).toBe(400); }); it("200 quand le controller renvoie un rapport (mode transcript)", async () => { correctEOMock.mockResolvedValue({ data: { score: 14, nclc: 9, simulation_id: "s1", diagnostic: "d", }, }); const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s1", tache: "EO_T1", transcript: "Bonjour je m appelle Pierre", }), }); expect(res.status).toBe(200); const body = await res.json(); expect(body.score).toBe(14); expect(correctEOMock).toHaveBeenCalledWith( expect.objectContaining({ simulationId: "s1", tache: "EO_T1", nclcCible: 9, transcript: "Bonjour je m appelle Pierre", }), expect.any(Object), ); }); it("200 mode batch audio (transmet audioBase64 + mimeType au controller)", async () => { correctEOMock.mockResolvedValue({ data: { score: 14, nclc: 9, simulation_id: "s-audio", diagnostic: "d" }, }); const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s-audio", tache: "EO_T1", audioBase64: "AAAA", mimeType: "audio/webm", }), }); expect(res.status).toBe(200); expect(correctEOMock).toHaveBeenCalledWith( expect.objectContaining({ simulationId: "s-audio", tache: "EO_T1", nclcCible: 9, audioBase64: "AAAA", mimeType: "audio/webm", }), expect.any(Object), ); }); it("200 avec nclc_cible=10 transmis au controller", async () => { correctEOMock.mockResolvedValue({ data: { score: 16, nclc: 10, simulation_id: "s2", diagnostic: "d" }, }); const app = buildApp(); const res = await app.request("/corrections/eo", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ simulationId: "s2", tache: "EO_T1", transcript: "Bonjour", nclc_cible: 10, }), }); expect(res.status).toBe(200); expect(correctEOMock).toHaveBeenCalledWith( expect.objectContaining({ simulationId: "s2", nclcCible: 10, transcript: "Bonjour", }), expect.any(Object), ); }); });