import { beforeEach, describe, expect, it, vi } from "vitest"; import type { TrpcContext } from "./_core/context"; const db = vi.hoisted(() => ({ getDisputedInvoices: vi.fn(), getInvoiceById: vi.fn(), updateInvoice: vi.fn() })); vi.mock("./db", () => db); import { listDisputes, setDispute } from "./invoiceDisputes"; import { router } from "./_core/trpc"; const routes = router({ listDisputes, setDispute }); function caller(role = "user", id = 2) { return routes.createCaller({ user: { id, role }, req: {}, res: {} } as TrpcContext); } beforeEach(() => { vi.clearAllMocks(); db.getInvoiceById.mockResolvedValue({ id: 10, userId: 2, isDisputed: 0, disputeComment: "existant", bapValidated: 1, exportStatus: "exported" }); db.getDisputedInvoices.mockResolvedValue([]); }); describe("Factures litiges", () => { it("ajoute le litige et son commentaire sans modifier BAP, score ou export", async () => { await caller().setDispute({ id: 10, isDisputed: true, comment: " À vérifier " }); expect(db.updateInvoice).toHaveBeenCalledWith(10, { isDisputed: 1, disputeComment: "À vérifier" }); }); it("modifie le commentaire", async () => { await caller().setDispute({ id: 10, isDisputed: true, comment: "Nouveau commentaire" }); expect(db.updateInvoice).toHaveBeenCalledWith(10, { isDisputed: 1, disputeComment: "Nouveau commentaire" }); }); it.each([null, "", " "])("efface le commentaire avec %s sans retirer le litige", async comment => { await caller().setDispute({ id: 10, isDisputed: true, comment }); expect(db.updateInvoice).toHaveBeenCalledWith(10, { isDisputed: 1, disputeComment: null }); }); it("retire le litige sans supprimer la facture ni son commentaire", async () => { await caller().setDispute({ id: 10, isDisputed: false }); expect(db.updateInvoice).toHaveBeenCalledWith(10, { isDisputed: 0 }); }); it("empêche l’accès à une facture d’un autre utilisateur", async () => { await expect(caller("user", 3).setDispute({ id: 10, isDisputed: true })).rejects.toMatchObject({ code: "NOT_FOUND" }); expect(db.updateInvoice).not.toHaveBeenCalled(); }); it("autorise l’administrateur sur les factures globales", async () => { await caller("admin", 3).setDispute({ id: 10, isDisputed: true }); expect(db.updateInvoice).toHaveBeenCalledOnce(); await caller("admin", 3).listDisputes(); expect(db.getDisputedInvoices).toHaveBeenCalledWith(undefined); }); it("limite la liste aux factures de l’utilisateur", async () => { await caller().listDisputes(); expect(db.getDisputedInvoices).toHaveBeenCalledWith(2); }); it("interdit la modification en lecture seule", async () => { await expect(caller("readonly").setDispute({ id: 10, isDisputed: true })).rejects.toMatchObject({ code: "FORBIDDEN" }); expect(db.updateInvoice).not.toHaveBeenCalled(); }); it("exige une session et limite les commentaires à 5000 caractères", async () => { await expect(routes.createCaller({ user: null } as TrpcContext).listDisputes()).rejects.toMatchObject({ code: "UNAUTHORIZED" }); await expect(caller().setDispute({ id: 10, isDisputed: true, comment: "x".repeat(5001) })).rejects.toMatchObject({ code: "BAD_REQUEST" }); }); });