import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { makeGraph, makeNode } from "@/lib/graph/schema.js"; const generateReconstruction = vi.fn(); vi.mock("@/lib/llm/provider", () => ({ getProvider: () => ({ generateReconstruction }), getProviderModelName: () => "test-model", })); vi.mock("@/lib/supabase/api-auth.js", () => ({ withAuthenticatedApi: (handler) => handler, })); function makeRecruitmentGraph() { const target = makeNode({ id: "recruitment-target", label: "Staffing certainty gap", description: "Whether additional skilled staff needed for expansion can be recruited", kind: "unknown", status: "unknown", confidence: "medium", }); return makeGraph({ centralStatement: "We need to compare our current baseline against the comparable state before staffing is secured for expansion.", currentSummary: "Test summary", nodes: [target], edges: [], activeUnknownNodeId: target.id, resolvedNodeIds: [], }); } function makeHistoricalGraph() { const target = makeNode({ id: "historical-target", label: "Baseline conversion rate", description: "Need the previous baseline because the change cannot be assessed without it.", kind: "unknown", status: "unknown", confidence: "medium", }); return makeGraph({ centralStatement: "Assess the change against the comparable prior baseline.", currentSummary: "Test summary", nodes: [target], edges: [], activeUnknownNodeId: target.id, resolvedNodeIds: [], }); } function makeRequest(graph, targetNodeId = graph.nodes[0].id) { return new Request("http://localhost/api/focused-investigation/formulate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ situationGraph: graph, targetNodeId }), }); } describe("POST /api/focused-investigation/formulate semantic compatibility", () => { beforeEach(() => { generateReconstruction.mockReset(); }); afterEach(() => { vi.useRealTimers(); }); it("suppresses an assessor-incompatible strategy without changing the selected target or graph", async () => { const graph = makeRecruitmentGraph(); const snapshot = JSON.stringify(graph); generateReconstruction.mockResolvedValue({ response: { strategyKey: "baseline_reconstruction", relevance: "incompatible", }, }); const { POST } = await import("@/app/api/focused-investigation/formulate/route.js"); const response = await POST(makeRequest(graph)); const json = await response.json(); expect(response.status).toBe(200); expect(json.targetNodeId).toBe("recruitment-target"); expect(json.strategy).toBeNull(); expect(json.question).toBe( "What evidence would clarify whether additional skilled staff needed for expansion can be recruited?", ); expect(JSON.stringify(graph)).toBe(snapshot); expect(generateReconstruction).toHaveBeenCalledTimes(1); }); it("does not let a compatible semantic verdict authorize a grammatically incompatible baseline template", async () => { const graph = makeRecruitmentGraph(); generateReconstruction.mockResolvedValue({ response: { strategyKey: "baseline_reconstruction", relevance: "compatible", }, }); const { POST } = await import("@/app/api/focused-investigation/formulate/route.js"); const response = await POST(makeRequest(graph)); const json = await response.json(); expect(json.strategy).toBeNull(); expect(json.question.toLowerCase()).not.toContain("before whether"); expect(json.question).toContain("What evidence would clarify whether"); }); it("preserves a compatible historical strategy when its template is suitable", async () => { const graph = makeHistoricalGraph(); generateReconstruction.mockResolvedValue({ response: { strategyKey: "baseline_reconstruction", relevance: "compatible", }, }); const { POST } = await import("@/app/api/focused-investigation/formulate/route.js"); const response = await POST(makeRequest(graph)); const json = await response.json(); expect(json.strategy).toBe("baseline_reconstruction"); expect(json.question).toContain("What was the comparable state before"); }); it("treats invalid or mismatched assessment output as non-authorizing", async () => { const graph = makeRecruitmentGraph(); generateReconstruction.mockResolvedValue({ response: { strategyKey: "evidence_gathering", relevance: "compatible" }, }); const { POST } = await import("@/app/api/focused-investigation/formulate/route.js"); const response = await POST(makeRequest(graph)); const json = await response.json(); expect(json.strategy).toBeNull(); expect(json.question).toContain("What evidence would clarify whether"); }); it("treats provider unavailability as non-authorizing fallback", async () => { const graph = makeRecruitmentGraph(); generateReconstruction.mockRejectedValue( Object.assign(new Error("provider unavailable"), { code: "PROVIDER_UNAVAILABLE" }), ); const { POST } = await import("@/app/api/focused-investigation/formulate/route.js"); const response = await POST(makeRequest(graph)); const json = await response.json(); expect(response.status).toBe(200); expect(json.strategy).toBeNull(); expect(json.question).toContain("What evidence would clarify whether"); }); it("aborts the one assessment invocation at the route deadline and falls back", async () => { vi.useFakeTimers(); const graph = makeRecruitmentGraph(); let observedSignal; generateReconstruction.mockImplementation((_, __, ___, { signal }) => { observedSignal = signal; return new Promise((_, reject) => { signal.addEventListener("abort", () => { const error = new Error("assessment aborted"); error.name = "AbortError"; reject(error); }, { once: true }); }); }); const { POST } = await import("@/app/api/focused-investigation/formulate/route.js"); const responsePromise = POST(makeRequest(graph)); await vi.advanceTimersByTimeAsync(10000); const response = await responsePromise; const json = await response.json(); expect(observedSignal.aborted).toBe(true); expect(json.strategy).toBeNull(); expect(json.question).toContain("What evidence would clarify whether"); }); });