Some checks failed
Validation applicative / TypeScript, tests et build (push) Failing after 1m54s
119 lines
5.8 KiB
TypeScript
119 lines
5.8 KiB
TypeScript
import express from "express";
|
|
import request from "supertest";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("./_core/sdk", () => ({ sdk: { authenticateRequest: vi.fn() } }));
|
|
vi.mock("./db", () => ({
|
|
getSalaireLiasseByPeriod: vi.fn(),
|
|
createSalaireLiasse: vi.fn(),
|
|
getSalaireLiasseById: vi.fn(),
|
|
replaceSalaireLiasseIndex: vi.fn(),
|
|
}));
|
|
vi.mock("./storage", () => ({ storagePut: vi.fn(), storageGetBuffer: vi.fn() }));
|
|
vi.mock("pdf-parse/lib/pdf-parse.js", () => ({ default: vi.fn() }));
|
|
|
|
import { sdk } from "./_core/sdk";
|
|
import { createSalaireLiasse, getSalaireLiasseById, getSalaireLiasseByPeriod } from "./db";
|
|
import { encryptPayrollPdf } from "./payrollCrypto";
|
|
import { registerSalairesHttpRoutes } from "./salairesHttp";
|
|
import { storageGetBuffer, storagePut } from "./storage";
|
|
import pdfParse from "pdf-parse/lib/pdf-parse.js";
|
|
|
|
const EXTRACTED_TEXT = `##BULLETIN##05-2026##009999##DUPONT##TEST##
|
|
Matricule : 009999
|
|
Emploi : RESPONSABLE APPLICATIF Monsieur TEST DUPONT
|
|
Salaire de base 3 650.00
|
|
Salaire brut 3 650.00`;
|
|
|
|
function makeApp() {
|
|
const app = express();
|
|
registerSalairesHttpRoutes(app);
|
|
return app;
|
|
}
|
|
|
|
describe("routes HTTP Salaires", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
process.env.PAYROLL_ENCRYPTION_KEY = Buffer.alloc(32, 8).toString("base64");
|
|
(getSalaireLiasseByPeriod as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
});
|
|
|
|
it("interdit l'upload au profil non administrateur", async () => {
|
|
(sdk.authenticateRequest as ReturnType<typeof vi.fn>).mockResolvedValue({ id: 2, role: "standard" });
|
|
|
|
await request(makeApp())
|
|
.post("/api/salaires/liasses")
|
|
.field("annee", "2026")
|
|
.field("mois", "5")
|
|
.attach("file", Buffer.from("%PDF-1.7"), { filename: "mai.pdf", contentType: "application/pdf" })
|
|
.expect(403);
|
|
|
|
expect(createSalaireLiasse).not.toHaveBeenCalled();
|
|
expect(storagePut).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("chiffre et archive une liasse administrateur sans persister de donnée sensible", async () => {
|
|
(sdk.authenticateRequest as ReturnType<typeof vi.fn>).mockResolvedValue({ id: 1, role: "admin" });
|
|
(pdfParse as ReturnType<typeof vi.fn>).mockResolvedValue({ text: EXTRACTED_TEXT });
|
|
(storagePut as ReturnType<typeof vi.fn>).mockResolvedValue({ key: "salaires/2026/05/liasse_opaque.enc" });
|
|
(createSalaireLiasse as ReturnType<typeof vi.fn>).mockResolvedValue({ liasseId: 12, bulletins: 1 });
|
|
|
|
const response = await request(makeApp())
|
|
.post("/api/salaires/liasses")
|
|
.field("annee", "2026")
|
|
.field("mois", "5")
|
|
.attach("file", Buffer.from("%PDF-1.7 document de test"), { filename: "mai.pdf", contentType: "application/pdf" })
|
|
.expect(201);
|
|
|
|
expect(response.body).toMatchObject({ id: 12, bulletins: 1, statutExtraction: "ready" });
|
|
expect(createSalaireLiasse).toHaveBeenCalledWith(expect.objectContaining({
|
|
annee: 2026,
|
|
mois: 5,
|
|
importePar: 1,
|
|
stockageKey: "salaires/2026/05/liasse_opaque.enc",
|
|
bulletins: [expect.objectContaining({ matricule: "009999", brutMensuelCents: 365000 })],
|
|
}));
|
|
expect(JSON.stringify((createSalaireLiasse as ReturnType<typeof vi.fn>).mock.calls[0]?.[0])).not.toContain("N° SS");
|
|
});
|
|
|
|
it("sert un PDF déchiffré uniquement à un administrateur et sans mise en cache", async () => {
|
|
(sdk.authenticateRequest as ReturnType<typeof vi.fn>).mockResolvedValue({ id: 1, role: "admin" });
|
|
const plaintext = Buffer.from("%PDF-1.7 contenu de bulletin", "utf8");
|
|
const encrypted = encryptPayrollPdf(plaintext);
|
|
(getSalaireLiasseById as ReturnType<typeof vi.fn>).mockResolvedValue({ id: 30001, stockageKey: "opaque", ivBase64: encrypted.ivBase64, authTagBase64: encrypted.authTagBase64, annee: 2026, mois: 5 });
|
|
(storageGetBuffer as ReturnType<typeof vi.fn>).mockResolvedValue(encrypted.ciphertext);
|
|
|
|
const response = await request(makeApp()).get("/api/salaires/liasses/30001/pdf").expect(200);
|
|
|
|
expect(response.headers["cache-control"]).toContain("no-store");
|
|
expect(response.headers["x-content-type-options"]).toBe("nosniff");
|
|
expect(response.headers["content-type"]).toContain("application/pdf");
|
|
expect(response.body).toEqual(plaintext);
|
|
});
|
|
|
|
it("interdit la consultation d'une liasse au profil lecture seule", async () => {
|
|
(sdk.authenticateRequest as ReturnType<typeof vi.fn>).mockResolvedValue({ id: 3, role: "readonly" });
|
|
|
|
await request(makeApp()).get("/api/salaires/liasses/12/pdf").expect(403);
|
|
expect(getSalaireLiasseById).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("réindexe une liasse archivée sans téléverser ni remplacer son PDF", async () => {
|
|
const { replaceSalaireLiasseIndex } = await import("./db");
|
|
(sdk.authenticateRequest as ReturnType<typeof vi.fn>).mockResolvedValue({ id: 1, role: "admin" });
|
|
const encrypted = encryptPayrollPdf(Buffer.from("%PDF-1.7 contenu de bulletin", "utf8"));
|
|
(getSalaireLiasseById as ReturnType<typeof vi.fn>).mockResolvedValue({ id: 30001, stockageKey: "opaque", ivBase64: encrypted.ivBase64, authTagBase64: encrypted.authTagBase64, annee: 2026, mois: 5 });
|
|
(storageGetBuffer as ReturnType<typeof vi.fn>).mockResolvedValue(encrypted.ciphertext);
|
|
(pdfParse as ReturnType<typeof vi.fn>).mockResolvedValue({ text: EXTRACTED_TEXT });
|
|
(replaceSalaireLiasseIndex as ReturnType<typeof vi.fn>).mockResolvedValue({ bulletins: 1 });
|
|
|
|
await request(makeApp()).post("/api/salaires/liasses/30001/reindex").expect(200);
|
|
|
|
expect(replaceSalaireLiasseIndex).toHaveBeenCalledWith(30001, expect.objectContaining({
|
|
statutExtraction: "ready",
|
|
bulletins: [expect.objectContaining({ matricule: "009999" })],
|
|
}));
|
|
expect(storagePut).not.toHaveBeenCalled();
|
|
});
|
|
});
|