import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { cssDeclarations } from "./css-contract-test-support.ts"; import { homeSurface as page } from "./home-surface.ts"; const read = (relativePath: string) => readFileSync(new URL(relativePath, import.meta.url), "utf8"); const sidebar = read("../src/components/app-sidebar.tsx"); const sidebarRow = read("../src/components/sidebar-session-row.tsx"); const starter = read("../src/components/starter-home.tsx"); const styles = read("../src/app/globals.css"); const pageSource = read("../src/app/page.tsx"); const hook = read("../src/hooks/use-rectification-surface.ts"); const sessions = read("../src/hooks/use-session-management.ts"); const caseService = read("../src/lib/rectification-agentic/v9/case-service.ts"); test("history session open uses the bound skill identity instead of the live registry version", () => { // 原值:openRectificationCase 对 session 也把 resolveActiveSkillPackage() 交给 v2 // 新值:session 先 v1 取 Case,再读绑定身份,注册表核验后把绑定身份交给 v2 // 决策 1:打开 RPC 不再要求等于当前版本 assert.match(caseService, /request\.intent === "session"/); assert.match(caseService, /open_agentic_rectification_case"/); assert.match(caseService, /get_agentic_rectification_skill_identity"/); assert.match(caseService, /resolveExactSkillPackage\(/); assert.match(caseService, /skill_identity_missing/); const homepageOpen = caseService.slice( caseService.indexOf("export async function openRectificationCase"), caseService.indexOf("export async function getRectificationEntrySummary"), ); assert.match(homepageOpen, /intent === "homepage" \|\| request\.intent === "new"/); assert.match(homepageOpen, /open_agentic_rectification_case_v2/); }); test("skill_identity_mismatch maps to 409 instead of the 500 fallback", () => { // 原值:KNOWN_RPC_ERROR_CODES 没有 mismatch,落到 校正服务暂时不可用 // 新值:409 skill_identity_mismatch,文案要求刷新 // 决策 2:修完决策 1 后不应再出现;映射只为不再露 500 assert.match( caseService, /"agentic_rectification_skill_identity_mismatch", \{ status: 409, code: "skill_identity_mismatch", message: "该校正绑定的规则版本与当前不一致,请刷新后重试" \}/, ); }); test("a history-list open failure shows under the clicked row, not on the starter card", () => { // 原值:rectificationError 只由 starter-home 画;selectSession 不切会话,表现为点了没反应 // 新值:session 失败绑到被点的那一行;首页起始卡不重复同一条 // 决策 3:错误就地显示 // 原值:const [rectificationErrorSessionId, setRectificationErrorSessionId] = useState(...) // 新值:外壳 9 个校正字段合成一个 rectification 对象;errorSessionId 仍叫 rectificationErrorSessionId,setter 仍叫 setRectificationErrorSessionId // 原因:外壳也要读的校正状态合并成一个对象,散装 useState 下降 assert.match(pageSource, /errorSessionId: rectificationErrorSessionId,/); assert.match(pageSource, /setRectificationErrorSessionId,/); assert.match( pageSource, /rectificationError=\{rectificationErrorSessionId \? "" : rectificationError\}/, ); assert.match(pageSource, /openErrorSessionId=\{rectificationErrorSessionId\}/); assert.match( pageSource, /openErrorMessage=\{rectificationErrorSessionId \? rectificationErrorMessage : ""\}/, ); assert.match(starter, /rectificationError && !rectificationSurfaceOpen && \(/); assert.match(sidebar, /openErrorSessionId/); assert.match(sidebarRow, /error \? \(/); assert.match(sidebarRow, /className="session-open-error"/); assert.match(sidebarRow, /role="alert"/); assert.match(hook, /setRectificationErrorSessionId\(intent === "session" \? exactSessionId : null\)/); assert.match(sessions, /setRectificationErrorSessionId\(null\)/); const notice = cssDeclarations(".session-open-error", styles); assert.match(notice, /color:\s*var\(--color-danger\)/); assert.match(notice, /font-size:\s*var\(--type-overline\)/); assert.match(page, /openErrorSessionId=\{rectificationErrorSessionId\}/); });