Files
writer-work-flow/apps/web/lib/errors/messages.test.ts
Yaojia Wang 9a623688e8 fix(qa): 前端错误文案——按 API 信封 surface 真实错因,去掉通用 toast
新增 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 干净。
2026-06-24 18:01:15 +02:00

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("出错了,请稍后重试。");
});
});