新增 friendlyFromApiError(apiError):兼容业务信封 {error:{code,message}}(ARCH §7.1)
与 FastAPI 422 {detail:[...]},422 映射为友好 VALIDATION 文案(不泄露技术细节)。
接入三处此前吞掉错因、永显通用文案的调用点:
- useOutline(QA #2):原 env.error.code 漏读 422 detail,永显「大纲生成失败」。
- ChainPage / ChainAdjudication(QA #11):原 friendlyError(undefined) 永显通用 toast,
现 surface 真实 code(如 LLM_UNAVAILABLE→去设置 provider / CONFLICT→提示裁决)。
+ messages.test.ts 加 3 例(业务码带动作 / 422→VALIDATION / 未知形兜底)。
门禁绿:vitest / tsc / eslint 干净。
47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { friendlyError, friendlyFromApiError } from "./messages";
|
|
|
|
describe("friendlyError", () => {
|
|
it("maps LLM_UNAVAILABLE to copy + a settings action", () => {
|
|
const e = friendlyError("LLM_UNAVAILABLE", "fallback exhausted");
|
|
expect(e.text).toContain("未连接");
|
|
expect(e.actionHref).toBe("/settings/providers");
|
|
expect(e.actionLabel).toBe("去设置提供商");
|
|
});
|
|
|
|
it("maps NETWORK to friendly copy without leaking raw message", () => {
|
|
const e = friendlyError("NETWORK", "TypeError: Failed to fetch");
|
|
expect(e.text).toBe("网络中断,请检查连接后重试。");
|
|
expect(e.actionHref).toBeUndefined();
|
|
});
|
|
|
|
it("falls back to the raw message for an unknown code", () => {
|
|
const e = friendlyError("WEIRD_CODE", "something specific happened");
|
|
expect(e.text).toBe("something specific happened");
|
|
});
|
|
|
|
it("uses generic copy when code is unknown and no message given", () => {
|
|
expect(friendlyError(undefined).text).toBe("出错了,请稍后重试。");
|
|
expect(friendlyError("WEIRD_CODE", " ").text).toBe("出错了,请稍后重试。");
|
|
});
|
|
});
|
|
|
|
describe("friendlyFromApiError", () => {
|
|
it("surfaces a business error envelope code + action (QA #2/#11)", () => {
|
|
const e = friendlyFromApiError({ error: { code: "LLM_UNAVAILABLE", message: "x" } });
|
|
expect(e.text).toContain("未连接");
|
|
expect(e.actionHref).toBe("/settings/providers");
|
|
});
|
|
|
|
it("maps a FastAPI 422 detail envelope to friendly VALIDATION copy (no leak)", () => {
|
|
const e = friendlyFromApiError({ detail: [{ msg: "Input should be >= 1", loc: ["volume"] }] });
|
|
expect(e.text).toBe("提交内容有误,请检查后重试。");
|
|
});
|
|
|
|
it("falls back to generic copy for null/unknown shapes", () => {
|
|
expect(friendlyFromApiError(null).text).toBe("出错了,请稍后重试。");
|
|
expect(friendlyFromApiError({ weird: 1 }).text).toBe("出错了,请稍后重试。");
|
|
});
|
|
});
|