补齐发票验真测试覆盖(05-28-oa-test-coverage-gap-fill)
这个提交包含在:
@@ -0,0 +1,161 @@
|
||||
const { FEATURE_FLAG_KEYS } = require("../../../src/services/featureFlags");
|
||||
const { runVerificationForRecord } = require("../../../src/jobs/verificationJob");
|
||||
|
||||
const RECORD_ID = "11111111-1111-4111-8111-111111111111";
|
||||
const INVOICE_ID = "22222222-2222-4222-8222-222222222222";
|
||||
const DEPARTMENT_ID = "dept-finance";
|
||||
|
||||
function buildFlagRecord({ overrideMode = "force_on", rolloutType = "global", departmentIds = [] } = {}) {
|
||||
const now = new Date("2026-05-28T00:00:00.000Z");
|
||||
return {
|
||||
id: "flag-invoice-verification",
|
||||
flag_key: FEATURE_FLAG_KEYS.invoiceVerification,
|
||||
override_mode: overrideMode,
|
||||
rollout_type: rolloutType,
|
||||
department_ids: departmentIds,
|
||||
note: "unit-test",
|
||||
version: 1,
|
||||
created_at: now,
|
||||
created_by: null,
|
||||
updated_at: now,
|
||||
updated_by: null,
|
||||
};
|
||||
}
|
||||
|
||||
function buildInvoice(overrides = {}) {
|
||||
return {
|
||||
id: INVOICE_ID,
|
||||
invoice_code: "0440026",
|
||||
invoice_number: "00012890",
|
||||
total_amount: "1060.00",
|
||||
remarks: "verify-ok",
|
||||
status: "available",
|
||||
owner_employee: { department_id: DEPARTMENT_ID },
|
||||
linked_reimbursement: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRecord(overrides = {}) {
|
||||
return {
|
||||
id: RECORD_ID,
|
||||
status: "pending",
|
||||
invoice: buildInvoice(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPrisma({ lockRows = [{ id: RECORD_ID }], record = buildRecord(), flagRecord = buildFlagRecord() } = {}) {
|
||||
const tx = {
|
||||
$queryRaw: vi.fn(async () => lockRows),
|
||||
featureFlag: {
|
||||
findUnique: vi.fn(async () => flagRecord),
|
||||
},
|
||||
invoiceVerificationRecord: {
|
||||
findUnique: vi.fn(async () => record),
|
||||
update: vi.fn(async ({ data }) => ({ id: RECORD_ID, ...data })),
|
||||
},
|
||||
};
|
||||
|
||||
const prisma = {
|
||||
...tx,
|
||||
$transaction: vi.fn(async (callback) => callback(tx)),
|
||||
};
|
||||
|
||||
return { prisma, tx };
|
||||
}
|
||||
|
||||
describe("runVerificationForRecord", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("skips when verification record cannot be locked", async () => {
|
||||
const { prisma, tx } = buildPrisma({ lockRows: [] });
|
||||
|
||||
const result = await runVerificationForRecord(prisma, { invoiceVerificationEnabled: true }, RECORD_ID);
|
||||
|
||||
expect(result).toEqual({ skipped: true, reason: "record_not_found" });
|
||||
expect(tx.invoiceVerificationRecord.findUnique).not.toHaveBeenCalled();
|
||||
expect(tx.invoiceVerificationRecord.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("marks record as error when linked invoice is missing", async () => {
|
||||
const { prisma, tx } = buildPrisma({ record: { id: RECORD_ID, status: "pending", invoice: null } });
|
||||
|
||||
const result = await runVerificationForRecord(prisma, { invoiceVerificationEnabled: true }, RECORD_ID);
|
||||
|
||||
expect(result).toEqual({ skipped: true, reason: "invoice_not_found" });
|
||||
expect(tx.invoiceVerificationRecord.update).toHaveBeenCalledWith({
|
||||
where: { id: RECORD_ID },
|
||||
data: expect.objectContaining({
|
||||
status: "error",
|
||||
error_code: "invoice_not_found",
|
||||
error_message: "invoice not found",
|
||||
completed_at: expect.any(Date),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("skips records that are no longer pending", async () => {
|
||||
const { prisma, tx } = buildPrisma({ record: buildRecord({ status: "passed" }) });
|
||||
|
||||
const result = await runVerificationForRecord(prisma, { invoiceVerificationEnabled: true }, RECORD_ID);
|
||||
|
||||
expect(result).toEqual({ skipped: true, reason: "status_not_pending" });
|
||||
expect(tx.invoiceVerificationRecord.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("records feature-flag shutdown as a controlled verification error", async () => {
|
||||
const { prisma, tx } = buildPrisma();
|
||||
|
||||
const result = await runVerificationForRecord(prisma, { invoiceVerificationEnabled: false }, RECORD_ID);
|
||||
|
||||
expect(result).toEqual({
|
||||
skipped: false,
|
||||
status: "error",
|
||||
durationMs: 0,
|
||||
resultCode: "verification_disabled_by_flag",
|
||||
});
|
||||
expect(tx.invoiceVerificationRecord.update).toHaveBeenCalledWith({
|
||||
where: { id: RECORD_ID },
|
||||
data: expect.objectContaining({
|
||||
status: "error",
|
||||
error_code: "verification_disabled_by_flag",
|
||||
error_message: "env_hard_off",
|
||||
completed_at: expect.any(Date),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("persists provider error result without leaking raw invoice data", async () => {
|
||||
const record = buildRecord({
|
||||
invoice: buildInvoice({ remarks: "verify-error" }),
|
||||
});
|
||||
const { prisma, tx } = buildPrisma({ record });
|
||||
|
||||
const result = await runVerificationForRecord(
|
||||
prisma,
|
||||
{ invoiceVerificationEnabled: true, verificationProvider: "mock" },
|
||||
RECORD_ID,
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
skipped: false,
|
||||
status: "error",
|
||||
resultCode: "mock_transport_error",
|
||||
});
|
||||
expect(tx.invoiceVerificationRecord.update).toHaveBeenCalledWith({
|
||||
where: { id: RECORD_ID },
|
||||
data: expect.objectContaining({
|
||||
status: "error",
|
||||
result_code: "mock_transport_error",
|
||||
error_code: "mock_transport_error",
|
||||
error_message: "mock provider transport error",
|
||||
result_payload: expect.objectContaining({ provider: "mock" }),
|
||||
duration_ms: expect.any(Number),
|
||||
completed_at: expect.any(Date),
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户