import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { z } from "zod"; import Fastify from "fastify"; import jwt from "jsonwebtoken"; import prisma from "../config/database"; import { config } from "../config/env"; import receivedInvoicesRoutes from "../routes/admin/received-invoices"; import { CreateReceivedInvoiceSchema } from "../schemas/received-invoices.schema"; /** * Pinning tests for audit finding H5: Czech-format dates in the received- * invoice upload metadata must be rejected with a clean Czech 400 BEFORE any * NAS side effect — "15.03.2026" used to parse to Invalid Date (NaN * month/year written after the NAS save → 500 + orphaned file) and * "12.6.2026" silently filed under December (US month-first parsing). */ let app: ReturnType; let adminToken: string; beforeAll(async () => { app = Fastify({ logger: false }); await app.register(receivedInvoicesRoutes, { prefix: "/api/admin/received-invoices", }); const admin = await prisma.users.findFirst({ where: { roles: { name: "admin" } }, }); if (!admin) throw new Error("Test setup: admin user not found"); adminToken = jwt.sign( { sub: admin.id, username: admin.username, role: "admin" }, config.jwt.secret, { expiresIn: "15m" }, ); }); afterAll(async () => { if (app) await app.close(); await prisma.$disconnect(); }); function multipartUpload(meta: Record) { const boundary = "----vitestboundary"; const payload = [ `--${boundary}`, 'Content-Disposition: form-data; name="invoices"', "", JSON.stringify([meta]), `--${boundary}`, 'Content-Disposition: form-data; name="files"; filename="test.pdf"', "Content-Type: application/pdf", "", "%PDF-1.4 test", `--${boundary}--`, "", ].join("\r\n"); return app.inject({ method: "POST", url: "/api/admin/received-invoices", headers: { Authorization: `Bearer ${adminToken}`, "content-type": `multipart/form-data; boundary=${boundary}`, }, payload, }); } describe("received-invoice date handling (audit H5)", () => { const partialSchema = z.array(CreateReceivedInvoiceSchema.partial()); it("schema rejects Czech-format dates", () => { expect( partialSchema.safeParse([{ issue_date: "15.03.2026" }]).success, ).toBe(false); expect(partialSchema.safeParse([{ issue_date: "12.6.2026" }]).success).toBe( false, ); }); it("schema strips a trailing time part and normalizes '' to null", () => { const withTime = partialSchema.safeParse([ { issue_date: "2098-05-03T00:00:00" }, ]); expect(withTime.success).toBe(true); if (withTime.success) expect(withTime.data[0].issue_date).toBe("2098-05-03"); const empty = partialSchema.safeParse([{ issue_date: "" }]); expect(empty.success).toBe(true); if (empty.success) expect(empty.data[0].issue_date).toBeNull(); }); it("upload with a Czech-format issue_date returns 400 before any NAS write", async () => { const res = await multipartUpload({ supplier_name: "Test s.r.o.", amount: 121, vat_rate: 21, issue_date: "15.03.2026", }); expect(res.statusCode).toBe(400); expect(res.json().error).toContain("Datum"); }); it("upload with an impossible regex-passing date returns 400, not NaN month", async () => { const res = await multipartUpload({ supplier_name: "Test s.r.o.", amount: 121, vat_rate: 21, issue_date: "2098-99-99", }); expect(res.statusCode).toBe(400); expect(res.json().error).toContain("datum"); }); });