import { describe, expect, it } from "vitest"; import { investigationReportSchema } from "./investigation-report"; const base = { title: "调查报告", current_conclusion: "无法确定原因", conclusion_boundary: "仅有上游监测", evidence_refs: ["query-1"], recommendations: [], pending_items: [] }; const result = { ...base, stage: "final", outcome: "insufficient_data", stop_reason: "缺少采样", data_gaps: [ { gap_id: "g1", node_id: "read", missing_data: "末梢采样", scope: "投诉小区,投诉时段", impact: "无法确定异常来源", required_data: "该时段采样结果", evidence_refs: ["query-1"], blocking: true } ] }; describe("investigation report read contract", () => { it("preserves unknown historical facts instead of inventing a result or empty gap list", () => { const legacy = investigationReportSchema.parse(base); expect(legacy.outcome).toBeUndefined(); expect(legacy.data_gaps).toBeUndefined(); expect( investigationReportSchema.parse({ ...base, stage: null, outcome: null, data_gaps: null, stop_reason: null }).data_gaps ).toBeNull(); }); it("accepts an evidenced final insufficient-data result", () => { expect(investigationReportSchema.parse(result).outcome).toBe("insufficient_data"); }); it("reads a narrative diagram without requiring a legacy execution plan", () => { const value = investigationReportSchema.parse({ ...result, data_gaps: [{ ...result.data_gaps[0], node_id: null }], diagram: { title: "调查脉络", mermaid: 'flowchart TD\nA["查询"] --> B["判断"]', node_refs: [{ node_id: "A", label: "查询", evidence_refs: ["query-1"] }] } }); expect(value.diagram?.node_refs[0]?.action_refs).toEqual([]); expect(value.data_gaps?.[0]?.node_id).toBeNull(); }); it("reads legacy waits as saved input conditions without inferring live waiting", () => { const value = investigationReportSchema.parse({ ...base, diagram: { title: "等待结果", mermaid: "flowchart LR\nA[外部回执] --> B[人工复核] --> C[下一步]", node_refs: [{ node_id: "A", label: "外部回执", status: "waiting" }, { node_id: "B", label: "人工复核", status: "waiting_human" }, { node_id: "C", label: "下一步" }] } }); expect(value.diagram?.node_refs.map((node) => node.status)).toEqual(["needs_input", "needs_input", undefined]); }); it("reads host-generated cumulative graphs with failed attempts and failure reasons", () => { const value = investigationReportSchema.parse({ ...base, diagram: { title: "累计调查脉络", mermaid: 'flowchart TD\nold["首次读取(失败)"] --> retry["重新读取"]', node_refs: [{ node_id: "old", label: "首次读取", status: "failed", detail: "接口超时" }, { node_id: "retry", label: "重新读取", status: "completed" }], edges: [{ source: "old", target: "retry", label: "重试" }] } }); expect(value.diagram?.node_refs[0]).toMatchObject({ status: "failed", detail: "接口超时" }); expect(value.diagram?.edges).toEqual([{ source: "old", target: "retry", label: "重试" }]); }); it.each([ { outcome: "other" }, { stage: "interim" }, { stop_reason: null }, { data_gaps: [] }, { recommendations: ["自动派单"] }, { pending_items: ["等人工"] }, { outcome: "concluded" }, { data_gaps: [{ ...result.data_gaps[0], evidence_refs: [] }] }, { data_gaps: [result.data_gaps[0], result.data_gaps[0]] }, { schema: "invented" } ])("rejects inconsistent business facts %j", (changes) => { expect(() => investigationReportSchema.parse({ ...result, ...changes })).toThrow(); }); }); it("retains fallback text from a future component while reading the fixed report facts", () => { const value = investigationReportSchema.parse({ ...base, ui_blocks: [{ evidence_refs: ["query-1"], block: { version: "answer-ui@future", block_id: "future", fallback_text: "旧版客户端仍可阅读。", spec: { future: true }, } }] }); expect(value.current_conclusion).toBe(base.current_conclusion); expect(value.ui_blocks?.[0].block.fallback_text).toBe("旧版客户端仍可阅读。"); expect(investigationReportSchema.parse(base).ui_blocks).toBeUndefined(); });