Files
confidence-engine/tests/reconstruction.test.js
T

234 lines
7.3 KiB
JavaScript

import { describe, it, expect, vi } from "vitest";
import { reconstructionSchema } from "@/lib/reconstruction/schema";
import { parseReconstruction } from "@/lib/reconstruction/schema";
describe("reconstruction schema", () => {
it("validates a complete valid reconstruction", () => {
const input = {
observations: [{ id: "o1", description: "Saw smoke", confidence: "high" }],
reportedClaims: [{ id: "rc1", description: "He said the alarm went off", confidence: "medium", attributedTo: "John" }],
assumptions: [{ id: "a1", description: "It was a fire", confidence: "low" }],
entities: [{ id: "e1", description: "John", confidence: "high" }],
transitions: [{ id: "t1", description: "John left the room", confidence: "medium", entity: "John", previousState: "present", currentState: "gone", explanationStatus: "confirmed" }],
expectedButMissing: [{ id: "eb1", description: "No one called 911", confidence: "high" }],
presentButUnexpected: [{ id: "pb1", description: "The lights were on", confidence: "low" }],
contradictions: [{ id: "c1", description: "Said he was home but car is gone", confidence: "medium" }],
openUncertainties: [{ id: "ou1", description: "Who was in the room?", confidence: "high" }],
};
const result = reconstructionSchema.safeParse(input);
expect(result.success).toBe(true);
});
it("rejects invalid confidence values", () => {
const input = {
observations: [{ id: "o1", description: "test", confidence: "extreme" }],
reportedClaims: [],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
};
const result = reconstructionSchema.safeParse(input);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].message).toContain("Expected");
}
});
it("rejects missing required fields", () => {
const input = {
observations: [{ id: "o1" }],
reportedClaims: [],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
};
const result = reconstructionSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("rejects invalid confidence values in reportedClaims", () => {
const input = {
observations: [],
reportedClaims: [{ id: "rc1", description: "test", confidence: "very_high", attributedTo: null }],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
};
const result = reconstructionSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("rejects empty transitions", () => {
const input = {
observations: [],
reportedClaims: [],
assumptions: [],
entities: [],
transitions: [{ id: "t1", description: "", confidence: "high", entity: "", previousState: "", currentState: "", explanationStatus: "" }],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
};
const result = reconstructionSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("allows null attributedTo on reported claims", () => {
const input = {
observations: [],
reportedClaims: [{ id: "rc1", description: "Someone called it in", confidence: "medium", attributedTo: null }],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
};
const result = reconstructionSchema.safeParse(input);
expect(result.success).toBe(true);
});
});
describe("parseReconstruction", () => {
it("parses a raw JSON string", () => {
const raw = JSON.stringify({
observations: [{ id: "o1", description: "test", confidence: "high" }],
reportedClaims: [],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
});
const result = parseReconstruction(raw);
expect(result.observations[0].id).toBe("o1");
});
it("rejects malformed JSON string", () => {
expect(() => parseReconstruction("{invalid json")).toThrow(SyntaxError);
});
it("rejects valid JSON that fails schema validation", () => {
const raw = JSON.stringify({
observations: [{ id: "o1", description: "test", confidence: "extreme" }],
reportedClaims: [],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
});
expect(() => parseReconstruction(raw)).toThrow();
});
});
describe("empty scenario rejection", () => {
it("rejects empty string", () => {
const trimmed = "".trim();
expect(trimmed.length).toBe(0);
});
it("rejects whitespace-only string", () => {
const trimmed = " \n\t ".trim();
expect(trimmed.length).toBe(0);
});
});
describe("provider response parsing", () => {
it("handles Ollama generate response shape", async () => {
vi.stubGlobal("process", { env: { OLLAMA_BASE_URL: "http://localhost:11434" } });
const mockResponse = JSON.stringify({
observations: [{ id: "o1", description: "test", confidence: "high" }],
reportedClaims: [],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
});
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ response: mockResponse }),
});
const { getProvider } = await import("@/lib/llm/provider");
const provider = new getProvider().constructor ? null : getProvider();
// The provider is instantiated in getProvider
expect(true).toBe(true);
});
it("handles raw JSON object response", () => {
const parsed = parseReconstruction({
observations: [],
reportedClaims: [{ id: "rc1", description: "he said", confidence: "medium", attributedTo: "Alice" }],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
});
expect(parsed.reportedClaims[0].attributedTo).toBe("Alice");
});
});
describe("malformed model output", () => {
it("throws on non-JSON string", () => {
expect(() => parseReconstruction("hello world")).toThrow(SyntaxError);
});
it("throws on JSON without required fields", () => {
const raw = JSON.stringify({ notTheRightStructure: true });
expect(() => parseReconstruction(raw)).toThrow();
});
it("handles empty arrays for all categories", () => {
const result = parseReconstruction({
observations: [],
reportedClaims: [],
assumptions: [],
entities: [],
transitions: [],
expectedButMissing: [],
presentButUnexpected: [],
contradictions: [],
openUncertainties: [],
});
expect(result.observations.length).toBe(0);
});
});