fix: 完善动态错误指引与发票日期校验(09-05-error-explainability-payment-readiness)

这个提交包含在:
2026-09-16 12:56:27 +08:00
父节点 b7d2285d8d
当前提交 3516d7ec04
共修改 28 个文件,包含 791 行新增和 616 行删除
@@ -130,6 +130,28 @@ async function buildApp({
}
describe("invoicePool upload route", () => {
it.each([
["", [], 1024, "empty_file"],
["%PDF-1.4 synthetic", [["ocr_type", "unsupported"]], 1024, "ocr_type_invalid"],
["%PDF-1.4 synthetic", [], 8, "upload_too_large"],
])("explains invalid upload without files, records or jobs: %s", async (content, fields, maxUploadSizeBytes, reason) => {
const bossSend = vi.fn();
const { app, prisma, tempDir } = await buildApp({ ocrProvider: "mock", jobDriver: "pgboss", bossSend, maxUploadSizeBytes });
try {
const input = buildMultipartRequest({ filename: "synthetic.pdf", contentType: "application/pdf", content, fields });
const response = await app.inject({ method: "POST", url: "/api/invoice-pool/upload", headers: { authorization: "Bearer test", ...input.headers }, payload: input.payload });
expect(response.statusCode).toBe(400);
expect(response.json().error.details.reasonCode).toBe(`invoice_pool.${reason}`);
expect(prisma.invoicePool.create).not.toHaveBeenCalled();
expect(prisma.auditLog.create).not.toHaveBeenCalled();
expect(bossSend).not.toHaveBeenCalled();
expect(await fs.readdir(tempDir)).toEqual([]);
} finally {
await app.close();
await fs.rm(tempDir, { recursive: true, force: true });
}
});
it("consumes the file stream before reading trailing multipart fields", async () => {
const { app, prisma, tempDir } = await buildApp();
@@ -261,7 +283,7 @@ describe("invoicePool upload route", () => {
expect.objectContaining({
code: "VALID_001",
message: "请上传 PDF 发票文件",
details: { file: "pdf_invoice_required" },
details: { file: "pdf_invoice_required", reasonCode: "invoice_pool.pdf_required" },
}),
);
expect(prisma.invoicePool.create).not.toHaveBeenCalled();
@@ -302,6 +324,7 @@ describe("invoicePool upload route", () => {
message: "PDF 发票不能超过 7 MiB",
details: {
file: "ocr_pdf_too_large",
reasonCode: "invoice_pool.ocr_pdf_too_large",
maxBytes: 7 * 1024 * 1024,
},
}),