文件
hyb-oa/server/tests/unit/services/paymentReceiptOcr.test.js
wangsli 8d08d46d16
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
fix: 增加回单核对与付款登记门禁(09-08-reimbursement-payment-closeout)
2026-09-15 07:21:55 +08:00

88 行
4.5 KiB
JavaScript

const { PDFDocument } = require("pdf-lib");
const sharp = require("sharp");
const { recognizePaymentReceipt } = require("../../../src/services/paymentReceiptOcr");
async function pdfBuffer(pages = 1) {
const pdf = await PDFDocument.create();
for (let page = 0; page < pages; page += 1) pdf.addPage();
return Buffer.from(await pdf.save());
}
function providerResult() {
return { BankSlipInfos: Object.entries({
小写金额: "10.00", 币别: "人民币", 付款人账号: "000011112222",
收款人账号: "000033334444", 单据状态: "交易成功", 凭证号: "SYN-REFERENCE",
}).map(([Name, Value]) => ({ Name, Value })), RequestId: "not-persisted", RawSensitive: "not-persisted" };
}
describe("dedicated payment receipt OCR", () => {
test("processes every PDF page without public URLs or invoice writes", async () => {
const BankSlipOCR = vi.fn().mockResolvedValue(providerResult());
const result = await recognizePaymentReceipt({
buffer: await pdfBuffer(2), mimeType: "application/pdf", createClient: () => ({ BankSlipOCR }),
});
expect(result).toMatchObject({ status: "recognized", pagesExpected: 2, pagesProcessed: 2 });
expect(BankSlipOCR).toHaveBeenCalledTimes(2);
expect(BankSlipOCR.mock.calls.map(([request]) => request.PdfPageNumber)).toEqual([1, 2]);
expect(BankSlipOCR.mock.calls.every(([request]) => request.ImageUrl === undefined)).toBe(true);
expect(result.pages[0].amount).toBe("1000");
expect(JSON.stringify(result)).not.toContain("not-persisted");
});
test("does not claim completeness after a later page fails or retry automatically", async () => {
const BankSlipOCR = vi.fn().mockResolvedValueOnce(providerResult()).mockRejectedValueOnce(new Error("sensitive error"));
const result = await recognizePaymentReceipt({
buffer: await pdfBuffer(2), mimeType: "application/pdf", createClient: () => ({ BankSlipOCR }),
});
expect(result).toMatchObject({ status: "ocr_failed", pagesExpected: 2, pagesProcessed: 1, reason: "receipt_ocr_request_failed" });
expect(BankSlipOCR).toHaveBeenCalledTimes(2);
expect(JSON.stringify(result)).not.toContain("sensitive error");
});
test("preserves unknown fields as unresolved rather than success", async () => {
const result = await recognizePaymentReceipt({
buffer: await pdfBuffer(), mimeType: "application/pdf",
createClient: () => ({ BankSlipOCR: async () => ({ BankSlipInfos: [] }) }),
});
expect(result.status).toBe("needs_review");
expect(result.pages[0].issues).toContain("response_invalid");
});
test("rejects invalid PDF before creating cloud client", async () => {
const createClient = vi.fn();
const result = await recognizePaymentReceipt({ buffer: Buffer.from("not pdf"), mimeType: "application/pdf", createClient });
expect(result.reason).toBe("receipt_decode_failed");
expect(createClient).not.toHaveBeenCalled();
});
test("limits pages before any external call", async () => {
const createClient = vi.fn();
const result = await recognizePaymentReceipt({ buffer: await pdfBuffer(21), mimeType: "application/pdf", createClient });
expect(result.reason).toBe("receipt_page_limit");
expect(createClient).not.toHaveBeenCalled();
});
test("converts existing WEBP uploads to supported PNG in memory", async () => {
const buffer = await sharp({ create: { width: 10, height: 10, channels: 3, background: "white" } }).webp().toBuffer();
const BankSlipOCR = vi.fn().mockResolvedValue(providerResult());
const result = await recognizePaymentReceipt({ buffer, mimeType: "image/webp", createClient: () => ({ BankSlipOCR }) });
expect(result.status).toBe("recognized");
expect(BankSlipOCR.mock.calls[0][0].IsPdf).toBe(false);
const sent = Buffer.from(BankSlipOCR.mock.calls[0][0].ImageBase64, "base64");
expect((await sharp(sent).metadata()).format).toBe("png");
});
test("missing credentials do not silently disable OCR", async () => {
const result = await recognizePaymentReceipt({ buffer: await pdfBuffer(), mimeType: "application/pdf", config: {} });
expect(result).toMatchObject({ status: "ocr_failed", reason: "receipt_ocr_unavailable" });
});
test.each(["off", "mock"])("provider %s never falls through to real Tencent", async (ocrProvider) => {
const result = await recognizePaymentReceipt({
buffer: await pdfBuffer(), mimeType: "application/pdf",
config: { ocrProvider, tencentSecretId: "synthetic", tencentSecretKey: "synthetic" },
});
expect(result).toMatchObject({ status: "ocr_failed", reason: "receipt_ocr_unavailable" });
});
});