feat: ajouter les factures litiges et leurs commentaires (recette)
Some checks failed
Validation applicative / TypeScript, tests et build (push) Failing after 2m14s

This commit is contained in:
Manus
2026-11-03 14:45:05 +00:00
parent 5f6c61d2f0
commit a6d90a90c3
16 changed files with 3320 additions and 13 deletions

View File

@@ -0,0 +1,55 @@
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" });
});
});