import { describe, expect, it, vi } from "vitest"; // ── Domain fixtures ─────────────────────────────────────────── const MOCK_SITUATION_GRAPH = { nodes: [{ id: "n-q1", label: "Query Q1", kind: "question", status: "unknown" }], edges: [], }; const DISTINCTIVE_GRAPH = { __distinctive_graph__: true }; function makePreparedEpisode(overrides = {}) { return { situationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", turns: [{ contributionId: "contrib-0001", sequence: 1, question: "Q?", answer: "A?" }], eligibleCanonicalFindings: [{ findingId: "f-1", proposition: "P1" }], excludedFindingProvenance: [], ...overrides, }; } function makeProposal() { return { addedNodes: [], updatedNodes: [{ nodeId: "n-q1", newStatus: "resolved" }], addedEdges: [], removedEdgeIds: [], resolvedUnknownNodeIds: [], affectedNodeIds: [], selectedQuestion: null, }; } // ── Import the testable pipeline ─────────────────────────────── import { executeEpisodeDone } from "@/components/scenario-form.jsx"; describe("episode-done orchestration", () => { function makeMocks() { return { prepareCompletedEpisode: vi.fn().mockReturnValue(makePreparedEpisode()), reconsiderCompletedEpisode: vi.fn().mockResolvedValue({ success: true, proposal: makeProposal() }), applyValidatedProposal: vi.fn().mockResolvedValue({ success: true, updatedSituationGraph: DISTINCTIVE_GRAPH }), }; } it("1. Successful path order", async () => { const m = makeMocks(); const order = []; const trackReconsider = vi.fn().mockImplementation(async (...args) => { order.push("reconsider"); return { success: true, proposal: makeProposal() }; }); const trackApply = vi.fn().mockImplementation(async (...args) => { order.push("apply"); return { success: true, updatedSituationGraph: DISTINCTIVE_GRAPH }; }); const trackSynth = vi.fn().mockImplementation(async (...args) => { order.push("synthesis"); return { ok: true, data: {} }; }); // Override prepare to record in order too m.prepareCompletedEpisode.mockImplementation(() => { order.push("prepare"); return makePreparedEpisode(); }); await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [{ id: "c-1" }], findings: [], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: trackReconsider, applyValidatedProposal: trackApply, synthesizeFn: trackSynth, setResult: vi.fn(), }); // Verify exact order expect(order[0]).toBe("prepare"); expect(order[1]).toBe("reconsider"); expect(order[2]).toBe("apply"); expect(order[3]).toBe("synthesis"); }); it("2. Correct prepared episode input", async () => { const m = makeMocks(); const findings = [{ id: "f-1", proposition: "P1" }]; const contributions = [{ id: "c-1", question: "Q?", answer: "A?" }]; await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: contributions, findings, prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }), setResult: vi.fn(), }); expect(m.prepareCompletedEpisode).toHaveBeenCalledWith({ situationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", contributions, findings, }); }); it("3. Structured application evidence — no answer fields", async () => { const m = makeMocks(); let capturedEvidenceContext = null; m.applyValidatedProposal.mockImplementation(async (args) => { capturedEvidenceContext = args?.evidenceContext; return { success: true, updatedSituationGraph: DISTINCTIVE_GRAPH }; }); await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [], findings: [], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }), setResult: vi.fn(), }); expect(capturedEvidenceContext?.isCompletedEpisode).toBe(true); expect(capturedEvidenceContext?.episodeEvidence).toBeDefined(); const keys = Object.keys(capturedEvidenceContext || {}); expect(keys).not.toContain("answer"); expect(keys).not.toContain("syntheticAnswer"); expect(keys).not.toContain("combinedAnswer"); expect(keys).not.toContain("lastAnswer"); }); it("4. nextGraph used for synthesis (not stale state)", async () => { const m = makeMocks(); let synthesizedGraph = null; m.applyValidatedProposal.mockResolvedValue({ success: true, updatedSituationGraph: DISTINCTIVE_GRAPH }); await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [], findings: [{ id: "f-1" }], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: (graph) => { synthesizedGraph = graph; return { ok: true, data: {} }; }, setResult: vi.fn(), }); expect(synthesizedGraph).toBe(DISTINCTIVE_GRAPH); }); it("5. Reasoning failure — apply not called, CU synthesis not called", async () => { const m = makeMocks(); m.reconsiderCompletedEpisode.mockResolvedValue({ success: false, stage: "provider" }); const result = await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [], findings: [], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: vi.fn(), setResult: vi.fn(), }); expect(result.success).toBe(false); expect(m.applyValidatedProposal).not.toHaveBeenCalled(); expect(m.prepareCompletedEpisode).toHaveBeenCalledTimes(1); expect(m.reconsiderCompletedEpisode).toHaveBeenCalledTimes(1); }); it("6. Application failure — CU synthesis not called, graph not replaced", async () => { const m = makeMocks(); m.applyValidatedProposal.mockResolvedValue({ success: false, stage: "proposal_compatibility" }); const result = await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [], findings: [], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: vi.fn(), setResult: vi.fn(), }); expect(result.success).toBe(false); }); it("7. Synthesis failure — nextGraph remains installed", async () => { const m = makeMocks(); const setResult = vi.fn(); await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [], findings: [], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: vi.fn().mockReturnValue({ ok: false }), setResult, }); // Graph was installed despite synthesis failure expect(setResult).toHaveBeenCalledTimes(1); const updater = setResult.mock.calls[0][0]; const updatedState = updater(null); expect(updatedState.situationGraph).toBe(DISTINCTIVE_GRAPH); }); it("8. Exactly-once — each step invoked once per call", async () => { const m = makeMocks(); await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [], findings: [], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }), setResult: vi.fn(), }); expect(m.prepareCompletedEpisode).toHaveBeenCalledTimes(1); expect(m.reconsiderCompletedEpisode).toHaveBeenCalledTimes(1); expect(m.applyValidatedProposal).toHaveBeenCalledTimes(1); }); it("9. Legacy Done CU writer retired — pipeline does not use deterministic summary", async () => { const m = makeMocks(); const result = await executeEpisodeDone({ resultSituationGraph: MOCK_SITUATION_GRAPH, targetNodeId: "n-q1", focusedContributions: [], findings: [], prepareCompletedEpisode: m.prepareCompletedEpisode, reconsiderCompletedEpisode: m.reconsiderCompletedEpisode, applyValidatedProposal: m.applyValidatedProposal, synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }), setResult: vi.fn(), }); expect(result.success).toBe(true); expect(result.nextGraph).toBe(DISTINCTIVE_GRAPH); }); });