Files
confidence-engine/tests/graph/orchestrator.test.js
T

182 lines
5.5 KiB
JavaScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const mockAnalyseScenario = vi.fn();
vi.mock("@/lib/analysis.js", () => ({
analyseScenario: (...args) => mockAnalyseScenario(...args),
}));
function makeAnalysisResult(overrides = {}) {
return {
success: true,
validationStatus: "valid",
modelName: "llama3",
responseDurationMs: 321,
rawResponse: "{}",
promptVersion: "v0.3",
reconstruction: {
summary: "Revenue and complaints diverge",
actors: [],
systemsOrObjects: [],
expectedStates: [],
observedStates: [
{
id: "obs-1",
label: "Revenue up",
description: "Revenue up 15%",
confidence: "high",
},
],
differences: [],
knownTransitions: [],
unexplainedTransitions: [],
contradictions: [],
importantUnknowns: [
{
id: "unk-1",
label: "Complaint rate denominator",
description: "Need the denominator for complaint rate",
confidence: "high",
},
],
plausibleInterpretations: [],
},
evidence: [],
nextQuestion: {
id: "q-1",
question: "What denominator is being used for the complaint rate?",
},
...overrides,
};
}
describe("lib/graph/orchestrator startCase", () => {
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
});
it("passes a valid request through to analyseScenario", async () => {
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
const { startCase } = await import("@/lib/graph/orchestrator.js");
const result = await startCase({
scenario: "Revenue increased while complaint counts rose faster.",
promptVersion: "v0.3",
});
expect(result.success).toBe(true);
expect(mockAnalyseScenario).toHaveBeenCalledWith(
"Revenue increased while complaint counts rose faster.",
{ promptVersion: "v0.3" },
);
});
it("rejects invalid request input without throwing", async () => {
const { startCase } = await import("@/lib/graph/orchestrator.js");
const result = await startCase({ scenario: "" });
expect(result).toMatchObject({
success: false,
error: "Invalid start-case request",
statusCode: 400,
});
expect(result.validationErrors).toBeInstanceOf(Array);
expect(mockAnalyseScenario).not.toHaveBeenCalled();
});
it("builds a valid graph on successful analysis", async () => {
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
const { startCase } = await import("@/lib/graph/orchestrator.js");
const result = await startCase({ scenario: "Scenario text" });
expect(result.success).toBe(true);
expect(result.situationGraph.centralStatement).toBe("Scenario text");
expect(result.situationGraph.currentSummary).toContain("Nodes:");
expect(result.diagnostics).toMatchObject({
validationStatus: "valid",
modelName: "llama3",
graphReferenceValidation: { valid: true, errors: [] },
});
});
it("applies active unknown selection to the graph", async () => {
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
const { startCase } = await import("@/lib/graph/orchestrator.js");
const result = await startCase({ scenario: "Scenario text" });
expect(result.success).toBe(true);
expect(result.situationGraph.activeUnknownNodeId).toBeTruthy();
});
it("returns structured failure when graph reference validation fails", async () => {
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
const utils = await import("@/lib/graph/utils.js");
const validateSpy = vi
.spyOn(utils, "validateGraphReferences")
.mockReturnValue({
valid: false,
errors: ['Edge references non-existent toNodeId "missing"'],
});
const { startCase } = await import("@/lib/graph/orchestrator.js");
const result = await startCase({ scenario: "Scenario text" });
expect(result).toMatchObject({
success: false,
error: "Situation graph reference validation failed",
validationErrors: ['Edge references non-existent toNodeId "missing"'],
statusCode: 500,
});
expect(result.diagnostics.graphReferenceValidation.valid).toBe(false);
validateSpy.mockRestore();
});
it("preserves analysis/provider failure details", async () => {
mockAnalyseScenario.mockResolvedValue({
success: false,
error: "Provider unavailable",
errors: ["socket hang up"],
rawResponse: null,
modelName: "llama3",
responseDurationMs: 99,
promptVersion: "v0.3",
validationStatus: "invalid",
statusCode: 502,
});
const { startCase } = await import("@/lib/graph/orchestrator.js");
const result = await startCase({ scenario: "Scenario text" });
expect(result).toMatchObject({
success: false,
error: "Provider unavailable",
analysisErrors: ["socket hang up"],
statusCode: 502,
});
});
it("returns null selectedQuestion when analysis has no nextQuestion", async () => {
mockAnalyseScenario.mockResolvedValue(
makeAnalysisResult({ nextQuestion: undefined }),
);
const { startCase } = await import("@/lib/graph/orchestrator.js");
const result = await startCase({ scenario: "Scenario text" });
expect(result.success).toBe(true);
expect(result.selectedQuestion).toBeNull();
});
it("exports placeholder updateCase", async () => {
const { updateCase } = await import("@/lib/graph/orchestrator.js");
await expect(updateCase()).rejects.toThrow(
"updateCase is not implemented yet",
);
});
});