88 行
4.5 KiB
JavaScript
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" });
|
|
});
|
|
});
|