feat: define graph update proposal contract
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseGraphUpdateProposal } from "@/lib/graph/update-proposal.js";
|
||||
|
||||
function makeValidProposal(overrides = {}) {
|
||||
return {
|
||||
addedNodes: [],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: "n-unknown",
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "1.9 complaints per 100 units",
|
||||
reason: "The answer directly provides the normalized complaint rate.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: ["n-unknown"],
|
||||
affectedNodeIds: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("parseGraphUpdateProposal", () => {
|
||||
it("parses a valid proposal", () => {
|
||||
const result = parseGraphUpdateProposal(makeValidProposal());
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.updatedNodes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("fails on malformed JSON", () => {
|
||||
const result = parseGraphUpdateProposal("{not json");
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("fails when required update content is invalid", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
updatedNodes: [{ nodeId: "n-unknown" }],
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("removes null array entries and logs them", () => {
|
||||
const result = parseGraphUpdateProposal(
|
||||
JSON.stringify({
|
||||
...makeValidProposal(),
|
||||
addedNodes: [null],
|
||||
}),
|
||||
);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.addedNodes).toEqual([]);
|
||||
expect(result.normalisationsApplied).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ change: "Removed null array entry" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("fills missing optional arrays with empty arrays", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
updatedNodes: [],
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.addedNodes).toEqual([]);
|
||||
expect(result.proposal.addedEdges).toEqual([]);
|
||||
expect(result.normalisationsApplied.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("normalises confirmed enum alias and preserves IDs", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
...makeValidProposal(),
|
||||
addedNodes: [
|
||||
{
|
||||
id: "n-new",
|
||||
label: "Reported update",
|
||||
description: "A new reported claim",
|
||||
kind: "reported_statement",
|
||||
status: "supported",
|
||||
confidence: "medium",
|
||||
value: null,
|
||||
unit: null,
|
||||
evidenceIds: [],
|
||||
dependsOn: [],
|
||||
affects: [],
|
||||
parentId: null,
|
||||
childIds: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.addedNodes[0].kind).toBe("reported_claim");
|
||||
expect(result.proposal.addedNodes[0].id).toBe("n-new");
|
||||
});
|
||||
|
||||
it("unknown enum values still fail", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
...makeValidProposal(),
|
||||
addedNodes: [
|
||||
{
|
||||
id: "n-new",
|
||||
label: "Bad node",
|
||||
description: "Bad node",
|
||||
kind: "unsupported_kind",
|
||||
status: "supported",
|
||||
confidence: "medium",
|
||||
value: null,
|
||||
unit: null,
|
||||
evidenceIds: [],
|
||||
dependsOn: [],
|
||||
affects: [],
|
||||
parentId: null,
|
||||
childIds: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("does not invent a next question", () => {
|
||||
const result = parseGraphUpdateProposal(makeValidProposal());
|
||||
expect(result.proposal.nextQuestion).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user