import { describe, it, expect } from "vitest"; import { focusedDeconstruct, buildFocusedDeconstructPrompt, validateFocusedDeconstructSchema, focusedDeconstructJsonSchema, } from "@/lib/graph/focused-investigation.js"; const FAKE_TARGET_NODE_ID = "test-node-001"; const FAKE_LABEL = "Market demand signal"; const FAKE_DESCRIPTION = "Evidence that customers want this."; const FAKE_CENTRAL = "Should we launch the product now?"; const FAKE_QUESTION = "What evidence would clarify market demand?"; const FAKE_ANSWER = "Some evidence suggests demand."; function makeValidResponse() { return { targetNodeId: "model-returned-001", observations: ["Observation A"], uncertainties: ["Uncertainty B"], assumptions: ["Assumption C"], relationships: [{ from: "A", to: "B", type: "supports" }], possibleFollowUpQuestions: ["What next?"], }; } function makeInvalidResponse() { return { targetNodeId: "model-returned-001", observations: ["Obs"] }; } describe("focusedDeconstruct — success", () => { it("calls provider.generateReconstruction exactly once and returns success", async () => { let callCount = 0; const fake = { async generateReconstruction() { callCount++; return { response: makeValidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(callCount).toBe(1); expect(result.success).toBe(true); }); it("preserves observations from provider response", async () => { const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.observations)).toBe(true); expect(result.observations.length).toBeGreaterThan(0); }); it("preserves uncertainties from provider response", async () => { const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.uncertainties)).toBe(true); expect(result.uncertainties.length).toBeGreaterThan(0); }); it("preserves assumptions from provider response", async () => { const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.assumptions)).toBe(true); expect(result.assumptions.length).toBeGreaterThan(0); }); it("preserves relationships from provider response", async () => { const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.relationships)).toBe(true); expect(result.relationships.length).toBeGreaterThan(0); }); it("preserves possibleFollowUpQuestions from provider response", async () => { const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.possibleFollowUpQuestions)).toBe(true); expect(result.possibleFollowUpQuestions.length).toBeGreaterThan(0); }); it("preserves caller target identity (result.targetNodeId === request targetNodeId)", async () => { const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(result.targetNodeId).toBe(FAKE_TARGET_NODE_ID); }); it("passes correct prompt to provider", async () => { let capturedPrompt = null; const fake = { async generateReconstruction(prompt) { capturedPrompt = prompt; return { response: makeValidResponse() }; } }; await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); const expected = buildFocusedDeconstructPrompt({ targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(capturedPrompt).toBe(expected); }); it("passes modelName unchanged to provider", async () => { let capturedModel = null; const fake = { async generateReconstruction(prompt, receivedModel) { capturedModel = receivedModel; return { response: makeValidResponse() }; } }; await focusedDeconstruct({ provider: fake, modelName: "my-custom-model-v2", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(capturedModel).toBe("my-custom-model-v2"); }); it("passes focusedDeconstructJsonSchema as the schema argument", async () => { let capturedSchema = null; const fake = { async generateReconstruction(prompt, modelName, schema) { capturedSchema = schema; return { response: makeValidResponse() }; } }; await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(capturedSchema).toBe(focusedDeconstructJsonSchema); }); }) describe("focusedDeconstruct — validation failure", () => { it("returns failure result when provider response fails canonical validation", async () => { const fake = { async generateReconstruction() { return { response: makeInvalidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(result.success).toBe(false); }); it("validation errors remain available on failure result", async () => { const fake = { async generateReconstruction() { return { response: makeInvalidResponse() }; } }; const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.errors)).toBe(true); expect(result.errors.length).toBeGreaterThan(0); }); it("no second provider call occurs on validation failure", async () => { let callCount = 0; const fake = { async generateReconstruction() { callCount++; return { response: makeInvalidResponse() }; } }; await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(callCount).toBe(1); }); }) describe("focusedDeconstruct — provider failure", () => { it("propagates error with original identity/metadata intact", async () => { const fake = { async generateReconstruction() { const err = new Error("Connection refused to ollama endpoint"); err.name = "ProviderError"; err.providerApiPath = "/api/v1/completions"; err.code = "PROVIDER_UNAVAILABLE"; throw err; }, }; await expect(focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER })).rejects.toThrow("Connection refused to ollama endpoint"); }); })