实现腾讯通用票据OCR安全适配(07-28-travel-reimbursement-guide)
这个提交包含在:
@@ -43,6 +43,7 @@ async function buildApp({
|
||||
invoiceConflicts = [],
|
||||
statusRows = [],
|
||||
permissionCodes = ["invoice.upload"],
|
||||
maxUploadSizeBytes = 5 * 1024 * 1024,
|
||||
} = {}) {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "hyb-oa-r71-invoice-upload-"));
|
||||
const invoicePoolCreate = vi.fn(async ({ data }) => ({
|
||||
@@ -74,7 +75,7 @@ async function buildApp({
|
||||
app.decorate("config", {
|
||||
appTimezone: "Asia/Shanghai",
|
||||
storageDir: tempDir,
|
||||
maxUploadSizeBytes: 5 * 1024 * 1024,
|
||||
maxUploadSizeBytes,
|
||||
invoiceArchiveUnusedDays: 30,
|
||||
ocrProvider,
|
||||
jobDriver,
|
||||
@@ -121,7 +122,7 @@ async function buildApp({
|
||||
|
||||
await app.register(multipart, {
|
||||
attachFieldsToBody: false,
|
||||
limits: { files: 1, fileSize: 5 * 1024 * 1024 },
|
||||
limits: { files: 1, fileSize: maxUploadSizeBytes },
|
||||
});
|
||||
await app.register(invoicePoolRoutes, { prefix: "/api" });
|
||||
|
||||
@@ -271,6 +272,48 @@ describe("invoicePool upload route", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a PDF above the 7 MiB Tencent OCR input boundary before storage", async () => {
|
||||
const { app, prisma, tempDir } = await buildApp({
|
||||
maxUploadSizeBytes: 8 * 1024 * 1024,
|
||||
});
|
||||
|
||||
try {
|
||||
const requestPayload = buildMultipartRequest({
|
||||
filename: "invoice.pdf",
|
||||
contentType: "application/pdf",
|
||||
content: Buffer.alloc(7 * 1024 * 1024 + 1, 0x20),
|
||||
fields: [["ocr_type", "vat"]],
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/invoice-pool/upload",
|
||||
headers: {
|
||||
authorization: "Bearer test",
|
||||
...requestPayload.headers,
|
||||
},
|
||||
payload: requestPayload.payload,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json().error).toEqual(
|
||||
expect.objectContaining({
|
||||
code: "VALID_001",
|
||||
message: "PDF 发票不能超过 7 MiB",
|
||||
details: {
|
||||
file: "ocr_pdf_too_large",
|
||||
maxBytes: 7 * 1024 * 1024,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(prisma.invoicePool.create).not.toHaveBeenCalled();
|
||||
expect(prisma.auditLog.create).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await app.close();
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("reuses same-owner existing invoice instead of creating a duplicate file record", async () => {
|
||||
const existingInvoiceId = "22222222-2222-4222-8222-222222222222";
|
||||
const { app, prisma, tempDir } = await buildApp({
|
||||
|
||||
在新工单中引用
屏蔽一个用户