feat: 完成错误原因合同与付款就绪提示(09-05-error-explainability-payment-readiness)
这个提交包含在:
@@ -0,0 +1,66 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const ts = require("../../../../client/node_modules/typescript");
|
||||
const expectedMessages = require("../fixtures/error-reason-messages.json");
|
||||
const { getErrorReason } = require("../../../src/constants/errorReasons");
|
||||
|
||||
function loadClientModule(relativePath, cache = new Map()) {
|
||||
const filename = path.resolve(__dirname, "../../../../client/src", relativePath);
|
||||
if (cache.has(filename)) return cache.get(filename).exports;
|
||||
const module = { exports: {} };
|
||||
cache.set(filename, module);
|
||||
const output = ts.transpileModule(fs.readFileSync(filename, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
|
||||
}).outputText;
|
||||
const localRequire = (specifier) => loadClientModule(path.relative(path.resolve(__dirname, "../../../../client/src"), path.resolve(path.dirname(filename), `${specifier}.ts`)), cache);
|
||||
new Function("require", "module", "exports", output)(localRequire, module, module.exports);
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
describe("client business error projection", () => {
|
||||
const cache = new Map();
|
||||
const { resolveErrorMessage } = loadClientModule("lib/resolveErrorMessage.ts", cache);
|
||||
const { ApiError } = loadClientModule("api/types.ts", cache);
|
||||
const { paymentItemFailureMessage, paymentRiskFlagsForDisplay } = loadClientModule("lib/paymentBatchDisplay.ts", cache);
|
||||
const fallback = "操作未完成,请核对页面提示;如仍无法继续,请联系管理员协助处理。";
|
||||
|
||||
it.each(Object.entries(expectedMessages))("keeps exact registered and displayed content for %s", (reasonCode, expected) => {
|
||||
expect(getErrorReason(reasonCode).defaultMessage).toBe(expected);
|
||||
expect(resolveErrorMessage(new ApiError({ status: 409, code: "VALID_001", message: "private payload", details: { reasonCode } }))).toBe(expected);
|
||||
});
|
||||
|
||||
it("shows exact actionable payment reason rather than arbitrary server text", () => {
|
||||
expect(resolveErrorMessage(new ApiError({ status: 409, code: "VALID_001", message: "private payload", details: { reasonCode: "payment_batch.legal_entity_required" } })))
|
||||
.toBe("报销人尚未维护所属公司,请联系管理员补充后再安排付款。");
|
||||
});
|
||||
|
||||
it.each([undefined, "unknown.reason", "__proto__", "constructor"])("fails safely for unknown reason %s", (reasonCode) => {
|
||||
expect(resolveErrorMessage(new ApiError({ status: 400, code: "VALID_001", message: "private payload", details: { reasonCode, internal: "/private/secret" } }))).toBe(fallback);
|
||||
});
|
||||
|
||||
it("adds only a valid diagnostic identifier, never arbitrary request text", () => {
|
||||
const error = new ApiError({ status: 409, code: "VALID_001", message: "参数校验失败", requestId: "req-test-1" });
|
||||
expect(resolveErrorMessage(error)).toBe(`${fallback} 排障编号:req-test-1`);
|
||||
error.requestId = "/private/secret";
|
||||
expect(resolveErrorMessage(error)).toBe(fallback);
|
||||
});
|
||||
|
||||
it("does not surface generic validation or internal identifiers from plain errors", () => {
|
||||
expect(resolveErrorMessage(new Error("VALID_001: 参数校验失败"))).toBe(fallback);
|
||||
expect(resolveErrorMessage(new Error("记录失败 a1111111-1111-4111-8111-111111111111"), "请刷新后重试。")).toBe("请刷新后重试。");
|
||||
});
|
||||
|
||||
it("separates persisted failure text from item and risk-card UI projection", () => {
|
||||
const item = { id: "item", status: "failed", failureCode: "VALID_001", failureMessage: "参数校验失败 /private/hidden", openRiskFlags: [] };
|
||||
expect(paymentItemFailureMessage(item, "batch")).toBe(fallback);
|
||||
const risk = { riskCode: "payment_batch_execute_failed", detail: "参数校验失败 /private/hidden", payloadJson: { batchId: "batch", itemId: "item", reasonCode: "payment_batch.source_status_invalid" } };
|
||||
item.openRiskFlags = [risk];
|
||||
const expected = "来源单据尚未满足付款条件或已经付款,请刷新并核实审批与付款状态。";
|
||||
expect(paymentItemFailureMessage(item, "batch")).toBe(expected);
|
||||
expect(paymentItemFailureMessage(item, "different-batch")).toBe(fallback);
|
||||
expect(paymentRiskFlagsForDisplay([risk])[0].detail).toBe(expected);
|
||||
expect(risk.detail).toBe("参数校验失败 /private/hidden");
|
||||
risk.payloadJson.reasonCode = "unknown.reason";
|
||||
expect(paymentRiskFlagsForDisplay([risk])[0].detail).toBe(fallback);
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户