import test from "node:test"; import assert from "node:assert/strict"; import { auditCypherWrite, formatAuditLine, type CypherWriteAuditInput, } from "../audit.js"; const baseInput: Omit = { schema: { labels: new Set(["Person", "Organization", "Task", "KnowledgeDocument"]), relationshipTypes: new Set(["KNOWS", "PARTICIPANT", "REFERENCES", "MENTIONS", "PART_OF"]), }, agentName: "database-operator", sessionId: "session-abc", nodesCreated: 0, relsCreated: 0, orphanIds: [], }; test("audit: clean write with provenance + known types yields zero warnings", () => { const cypher = ` MATCH (p:Person {name: $name}), (o:Organization {name: $org}) MERGE (p)-[r:KNOWS]->(o) SET r.createdAt = datetime(), r.createdByAgent = $agent, r.createdByTool = 'graph-cypher-write', r.createdBySession = $sessionId `; const warnings = auditCypherWrite({ ...baseInput, cypher, relsCreated: 1 }); assert.deepEqual(warnings, []); }); test("audit: unknown edge type yields unknown-type-warning", () => { const cypher = "MATCH (a:Person), (b:Person) CREATE (a)-[:HAS_KNOWN]->(b)"; const warnings = auditCypherWrite({ ...baseInput, cypher, relsCreated: 1 }); const w = warnings.find((x) => x.kind === "unknown-type-warning"); assert.ok(w, "expected unknown-type-warning"); assert.equal(w.kind, "unknown-type-warning"); if (w.kind === "unknown-type-warning") { assert.equal(w.type, "HAS_KNOWN"); } }); test("audit: known APOC edge in YIELD does not trip unknown-type", () => { const cypher = "MATCH (a:Person), (b:Person) WHERE a.email = b.email AND id(a) < id(b) WITH a, b CALL apoc.refactor.mergeNodes([a, b]) YIELD node RETURN node"; const warnings = auditCypherWrite({ ...baseInput, cypher }); const unknownTypeWarn = warnings.find((x) => x.kind === "unknown-type-warning"); assert.equal(unknownTypeWarn, undefined); }); test("audit: CREATE with no provenance stamps yields missing-provenance-warning", () => { const cypher = "CREATE (n:Person {name: 'Foo'})"; const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 1 }); const w = warnings.find((x) => x.kind === "missing-provenance-warning"); assert.ok(w, "expected missing-provenance-warning"); if (w.kind === "missing-provenance-warning") { assert.equal(w.created, 1); assert.equal(w.stamped, 0); } }); test("audit: MERGE with createdBy* stamps does not trip missing-provenance", () => { const cypher = "MERGE (n:Person {email: $email}) ON CREATE SET n.createdByAgent = $agent, n.createdBySession = $sid"; const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 1 }); const w = warnings.find((x) => x.kind === "missing-provenance-warning"); assert.equal(w, undefined); }); test("audit: provenance check ignores stamps inside string literals", () => { const cypher = "CREATE (n:Person {bio: 'I have createdByAgent in my bio'})"; const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 1 }); const w = warnings.find((x) => x.kind === "missing-provenance-warning"); assert.ok(w, "expected missing-provenance-warning despite stamp-like substring in literal"); }); test("audit: idempotent MERGE that matched existing node (nodesCreated=0) emits no missing-provenance-warning", () => { // Reviewer-flagged false-positive case. MERGE (n:Person {email}) without // ON CREATE SET stamps — static count = 1, stamps = 0. If the node // already existed, upstream reports nodesCreated=0 and no provenance was // needed; the audit must not warn. const cypher = "MERGE (n:Person {email: $email})"; const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 0 }); const w = warnings.find((x) => x.kind === "missing-provenance-warning"); assert.equal(w, undefined, "must not warn when nodesCreated=0"); }); test("audit: orphanIds populated yields orphan-warning", () => { const warnings = auditCypherWrite({ ...baseInput, cypher: "CREATE (n:Person)", nodesCreated: 1, orphanIds: ["4:abc:1", "4:abc:2"], }); const w = warnings.find((x) => x.kind === "orphan-warning"); assert.ok(w, "expected orphan-warning"); if (w.kind === "orphan-warning") { assert.deepEqual(w.orphanIds, ["4:abc:1", "4:abc:2"]); } }); test("audit: empty orphanIds emits no orphan-warning", () => { const warnings = auditCypherWrite({ ...baseInput, cypher: "MATCH (a:Person), (b:Person) CREATE (a)-[:KNOWS]->(b)", relsCreated: 1, orphanIds: [], }); const w = warnings.find((x) => x.kind === "orphan-warning"); assert.equal(w, undefined); }); test("formatAuditLine: accepted line includes counters + provenance", () => { const line = formatAuditLine({ kind: "accepted", cypherPrefix: "MATCH (a:Person) CREATE", nodesCreated: 1, relsCreated: 4, agentName: "database-operator", sessionId: "abc-123", }); assert.match(line, /^\[graph-cypher-write\] accepted /); assert.match(line, /nodesCreated=1/); assert.match(line, /relsCreated=4/); assert.match(line, /agentName=database-operator/); assert.match(line, /sessionId=abc-123/); }); test("formatAuditLine: orphan-warning line lists orphanIds", () => { const line = formatAuditLine({ kind: "orphan-warning", cypherPrefix: "CREATE (n:Person)", orphanIds: ["4:abc:1", "4:abc:2"], }); assert.match(line, /^\[graph-cypher-write\] orphan-warning /); assert.match(line, /orphanIds=4:abc:1,4:abc:2/); }); test("formatAuditLine: unknown-type-warning names the type", () => { const line = formatAuditLine({ kind: "unknown-type-warning", cypherPrefix: "CREATE (a)-[:HAS_KNOWN]->(b)", type: "HAS_KNOWN", }); assert.match(line, /^\[graph-cypher-write\] unknown-type-warning /); assert.match(line, /type=HAS_KNOWN/); }); test("formatAuditLine: missing-provenance-warning shows created vs stamped counts", () => { const line = formatAuditLine({ kind: "missing-provenance-warning", cypherPrefix: "CREATE (n:Person)", created: 3, stamped: 1, }); assert.match(line, /^\[graph-cypher-write\] missing-provenance-warning /); assert.match(line, /created=3/); assert.match(line, /stamped=1/); });