实现报销材料连续预览(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -0,0 +1,25 @@
|
||||
const { mapInvoiceDocumentMeta } = require("../../../src/services/invoiceDocumentMeta");
|
||||
|
||||
describe("invoice document metadata", () => {
|
||||
it("exposes only supported document categories and a bounded subtype", () => {
|
||||
expect(mapInvoiceDocumentMeta({
|
||||
ocr_result: {
|
||||
result_json: {
|
||||
documentCategory: " electronic_train_ticket ",
|
||||
documentSubtype: `ElectronicTrainTicketFull${"x".repeat(80)}`,
|
||||
raw: { sensitive: true },
|
||||
},
|
||||
},
|
||||
})).toEqual({
|
||||
documentCategory: "electronic_train_ticket",
|
||||
documentSubtype: `ElectronicTrainTicketFull${"x".repeat(39)}`,
|
||||
});
|
||||
});
|
||||
|
||||
it("fails closed for unknown, blank, or malformed OCR metadata", () => {
|
||||
expect(mapInvoiceDocumentMeta({
|
||||
ocr_result: { result_json: { documentCategory: "unknown_invoice", documentSubtype: " " } },
|
||||
})).toEqual({ documentCategory: null, documentSubtype: null });
|
||||
expect(mapInvoiceDocumentMeta(null)).toEqual({ documentCategory: null, documentSubtype: null });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
const {
|
||||
MaterialPreviewError,
|
||||
createReimbursementMaterialPreviewStore,
|
||||
} = require("../../../src/services/reimbursementMaterialPreview");
|
||||
|
||||
describe("reimbursement material preview store", () => {
|
||||
it("keeps material and PDF page order in a bounded short-lived bundle", async () => {
|
||||
let currentTime = 1_000;
|
||||
const store = createReimbursementMaterialPreviewStore({
|
||||
now: () => currentTime,
|
||||
randomUUID: () => "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
||||
rasterizePdf: vi.fn().mockResolvedValue([Buffer.from("pdf-page-1"), Buffer.from("pdf-page-2")]),
|
||||
normalizeImage: vi.fn().mockResolvedValue(Buffer.from("image-page")),
|
||||
});
|
||||
|
||||
const created = await store.createBundle({
|
||||
actorId: "actor-1",
|
||||
reimbursementId: "reimbursement-1",
|
||||
expiresAt: 2_000,
|
||||
materials: [
|
||||
{ id: "invoice-1", materialType: "invoice", materialIndex: 1, mimeType: "application/pdf", fileSize: 10 },
|
||||
{ id: "attachment-1", materialType: "attachment", materialIndex: 1, mimeType: "image/png", fileSize: 10 },
|
||||
],
|
||||
readSource: async (material) => Buffer.from(material.id),
|
||||
});
|
||||
|
||||
expect(created.bundle.pages.map((page) => [page.materialType, page.materialIndex, page.pageNumber])).toEqual([
|
||||
["invoice", 1, 1],
|
||||
["invoice", 1, 2],
|
||||
["attachment", 1, 1],
|
||||
]);
|
||||
expect(store.getPage({ bundleId: created.bundleId, pageIndex: 1 })?.page.buffer.toString()).toBe("pdf-page-2");
|
||||
|
||||
currentTime = 2_001;
|
||||
expect(store.getPage({ bundleId: created.bundleId, pageIndex: 0 })).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects source and page limits without keeping a partial bundle", async () => {
|
||||
const store = createReimbursementMaterialPreviewStore({
|
||||
limits: { maxMaterials: 1, maxSourceBytes: 10, maxPages: 1 },
|
||||
rasterizePdf: vi.fn().mockResolvedValue([Buffer.from("one"), Buffer.from("two")]),
|
||||
});
|
||||
|
||||
await expect(store.createBundle({
|
||||
actorId: "actor-1",
|
||||
reimbursementId: "reimbursement-1",
|
||||
expiresAt: Date.now() + 1_000,
|
||||
materials: [
|
||||
{ id: "one", materialType: "invoice", materialIndex: 1, mimeType: "application/pdf", fileSize: 6 },
|
||||
{ id: "two", materialType: "attachment", materialIndex: 1, mimeType: "image/png", fileSize: 6 },
|
||||
],
|
||||
readSource: async () => Buffer.from("source"),
|
||||
})).rejects.toMatchObject({ code: "material_preview_material_limit" });
|
||||
|
||||
await expect(store.createBundle({
|
||||
actorId: "actor-1",
|
||||
reimbursementId: "reimbursement-1",
|
||||
expiresAt: Date.now() + 1_000,
|
||||
materials: [
|
||||
{ id: "one", materialType: "invoice", materialIndex: 1, mimeType: "application/pdf", fileSize: 6 },
|
||||
],
|
||||
readSource: async () => Buffer.from("source"),
|
||||
})).rejects.toBeInstanceOf(MaterialPreviewError);
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户