Checkpoint: Audit de robustesse et maintenabilité : calcul dynamique des tendances OPEX par véritable année N-1, filtrage/validation centralisés des codes établissement, transactions d'import et d'écriture, contraintes uniques CAPEX/OPEX avec déduplication, suppression effective de postes OPEX, nettoyage des gabarits et scripts morts, chargement différé XLSX, documentation métier et 22 tests passants.
This commit is contained in:
@@ -68,6 +68,9 @@ vi.mock("./db", async (importOriginal) => {
|
||||
listUsers: vi.fn(),
|
||||
listEtablissements: vi.fn(),
|
||||
getParametres: vi.fn(),
|
||||
upsertOpexBaseRepartition: vi.fn(),
|
||||
setOpexMontantsEtabBatch: vi.fn(),
|
||||
deleteOpexPoste: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -268,3 +271,74 @@ describe("parametres.get", () => {
|
||||
expect(result).toEqual({ seuil_fixes_ans: "5", cout_fixe: "850" });
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Tests robustesse OPEX ────────────────────────────────────────────────────
|
||||
|
||||
describe("opex.setBaseRepartition", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("normalise le code établissement avant de l'enregistrer", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ role: "standard" });
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
await caller.opex.setBaseRepartition({
|
||||
annee: 2026,
|
||||
etablissementCode: " 1001bpt ",
|
||||
etablissementNom: "FAM Saint Joseph",
|
||||
baseRepartition: 100,
|
||||
baseRepartitionHep: 0,
|
||||
modeManuel: false,
|
||||
});
|
||||
|
||||
expect(db.upsertOpexBaseRepartition).toHaveBeenCalledWith(expect.objectContaining({
|
||||
etablissementCode: "1001BPT",
|
||||
}));
|
||||
});
|
||||
|
||||
it("rejette les lignes de synthèse avant tout accès à la base", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ role: "standard" });
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
await expect(caller.opex.setBaseRepartition({
|
||||
annee: 2026,
|
||||
etablissementCode: "TOTAL",
|
||||
baseRepartition: 1,
|
||||
baseRepartitionHep: 0,
|
||||
modeManuel: false,
|
||||
})).rejects.toThrow("Code établissement OPEX invalide");
|
||||
expect(db.upsertOpexBaseRepartition).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("interdit toute écriture au profil readonly", async () => {
|
||||
const { ctx } = createAuthCtx({ role: "readonly" });
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
await expect(caller.opex.setBaseRepartition({
|
||||
annee: 2026,
|
||||
etablissementCode: "1001BPT",
|
||||
baseRepartition: 1,
|
||||
baseRepartitionHep: 0,
|
||||
modeManuel: false,
|
||||
})).rejects.toThrow("Accès en lecture seule");
|
||||
});
|
||||
});
|
||||
|
||||
describe("opex.deletePoste", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("supprime un poste et renvoie le résultat de la transaction métier", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
(db.deleteOpexPoste as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
const { ctx } = createAuthCtx({ role: "standard" });
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
await expect(caller.opex.deletePoste({ annee: 2027, id: 42 })).resolves.toEqual({ success: true });
|
||||
expect(db.deleteOpexPoste).toHaveBeenCalledWith(2027, 42);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user