fix: 完善动态错误指引与发票日期校验(09-05-error-explainability-payment-readiness)

这个提交包含在:
2026-09-16 12:56:27 +08:00
父节点 b7d2285d8d
当前提交 3516d7ec04
共修改 28 个文件,包含 791 行新增和 616 行删除
@@ -65,10 +65,45 @@ async function buildApp(permissionCodes) {
});
app.setErrorHandler((error, request, reply) => reply.status(error.statusCode ?? 500).send({ requestId: request.id, error: { code: error.code ?? "SYS_001", message: error.message, details: error.details ?? {} } }));
await app.register(paymentAccountsRoutes, { prefix: "/api" });
return { app, auditCreate, notificationCreateMany, paymentAccountCreate };
return { app, tx, auditCreate, notificationCreateMany, paymentAccountCreate };
}
describe("payment account routes", () => {
const validBody = { legalEntityId: LEGAL_ENTITY_ID, bankInstitutionId: BANK_ID, accountName: "合成测试公司", accountNumber: SYNTHETIC_ACCOUNT };
it.each([
["account_input_invalid", "/payment-accounts", { ...validBody, accountNumber: "invalid" }, 400],
["bank_input_invalid", "/bank-institutions", { code: "!", name: "" }, 400],
["reveal_reason_required", `/payment-accounts/${ACCOUNT_ID}/reveal`, { reason: "" }, 400],
["id_invalid", "/payment-accounts/invalid/reveal", { reason: "核对资料" }, 400],
["legal_entity_unavailable", "/payment-accounts", validBody, 409],
["bank_unavailable", "/payment-accounts", validBody, 409],
["legal_entity_change_forbidden", `/payment-accounts/${ACCOUNT_ID}/replace`, { ...validBody, legalEntityId: ACTOR_ID }, 400],
["bank_code_exists", "/bank-institutions", { code: "CCB", name: "合成银行" }, 409],
["bank_in_use", `/bank-institutions/${BANK_ID}/disable`, {}, 409],
])("explains %s without successful mutation or sensitive output", async (reason, url, payload, status) => {
const testApp = await buildApp(["*"]);
try {
if (reason === "legal_entity_unavailable") testApp.tx.legalEntity.findFirst.mockResolvedValue(null);
if (reason === "bank_unavailable") testApp.tx.bankInstitution.findFirst.mockResolvedValue(null);
if (reason === "bank_code_exists") testApp.tx.bankInstitution.create = vi.fn().mockRejectedValue({ code: "P2002" });
if (reason === "bank_in_use") {
testApp.tx.bankInstitution.findUnique = vi.fn().mockResolvedValue({ id: BANK_ID });
testApp.tx.paymentAccount.count = vi.fn().mockResolvedValue(1);
}
const response = await testApp.app.inject({ method: "POST", url: `/api${url}`, payload });
expect(response.statusCode).toBe(status);
expect(response.json().error.code).toBe("VALID_001");
expect(response.json().error.details.reasonCode).toBe(`payment_account.${reason}`);
const { getErrorReason } = require("../../../src/constants/errorReasons");
const expected = require("../fixtures/error-reason-messages.json");
expect(getErrorReason(`payment_account.${reason}`).defaultMessage).toBe(expected[`payment_account.${reason}`]);
expect(JSON.stringify(response.json())).not.toContain(SYNTHETIC_ACCOUNT);
expect(testApp.paymentAccountCreate).not.toHaveBeenCalled();
expect(testApp.tx.paymentAccount.update).not.toHaveBeenCalled();
expect(testApp.auditCreate).not.toHaveBeenCalled();
expect(testApp.notificationCreateMany).not.toHaveBeenCalled();
} finally { await testApp.app.close(); }
});
it("returns only a masked account on normal reads", async () => {
const testApp = await buildApp(["payment.account.read"]);
const response = await testApp.app.inject({ method: "GET", url: "/api/payment-accounts" });