import test from "node:test"; import assert from "node:assert/strict"; import { writeNodeWithEdges, type GraphRelationship } from "../index.js"; interface StubTx { run(cypher: string, params?: Record): Promise<{ records: { get(field: string): unknown }[]; summary?: { counters: { updates(): { relationshipsCreated: number } } }; }>; } function makeStubSession(targetId: string) { const tx: StubTx = { async run(cypher: string, params: Record = {}) { if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) { const ids = (params.ids as string[]) ?? []; const records = ids .filter((id) => id === targetId) .map((id) => ({ get(field: string) { if (field === "id") return id; if (field === "labels") return ["Account"]; throw new Error(`unknown field ${field}`); }, })); return { records }; } if (cypher.includes("CREATE (n:")) { return { records: [ { get(field: string) { if (field === "nodeId") return "new-element-id"; if (field === "nodeLabels") return ["Conversation"]; throw new Error(`unknown field ${field}`); }, }, ], }; } if (cypher.includes("CREATE (a)-[") || cypher.includes("CREATE (b)-[")) { return { records: [], summary: { counters: { updates: () => ({ relationshipsCreated: 1 }), }, }, }; } return { records: [] }; }, }; return { async executeWrite(work: (tx: StubTx) => Promise): Promise { return await work(tx); }, }; } const VALID_UUID = "12345678-9abc-def0-1234-56789abcdef0"; const TARGET_ID = "target-element-id"; const REL: GraphRelationship[] = [ { type: "PART_OF", direction: "outgoing", targetNodeId: TARGET_ID }, ]; function captureStderr(): { restore: () => void; output: () => string } { const original = process.stderr.write.bind(process.stderr); let buf = ""; (process.stderr as { write: (chunk: string | Uint8Array) => boolean }).write = (chunk: string | Uint8Array): boolean => { buf += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(); return true; }; return { restore: () => { process.stderr.write = original; }, output: () => buf, }; } test("rejects null accountId from non-system writer", async () => { const cap = captureStderr(); try { await assert.rejects( () => writeNodeWithEdges({ // eslint-disable-next-line @typescript-eslint/no-explicit-any session: makeStubSession(TARGET_ID) as any, labels: ["Conversation"], props: { accountId: null }, relationships: REL, createdBy: { agent: "test-agent", session: "s" }, expectedAccountId: VALID_UUID, }), /invalid-account-id/, ); cap.restore(); assert.match( cap.output(), /\[graph-write\] reject reason=invalid-account-id accountId=missing writer=test-agent\b/, ); } finally { cap.restore(); } }); test("rejects non-UUID accountId from non-system writer", async () => { const cap = captureStderr(); try { await assert.rejects( () => writeNodeWithEdges({ // eslint-disable-next-line @typescript-eslint/no-explicit-any session: makeStubSession(TARGET_ID) as any, labels: ["Conversation"], props: { accountId: "evil-leak" }, relationships: REL, createdBy: { agent: "test-agent", session: "s" }, expectedAccountId: VALID_UUID, }), /invalid-account-id/, ); cap.restore(); assert.match( cap.output(), /\[graph-write\] reject reason=invalid-account-id accountId=evil-lea writer=test-agent\b/, ); } finally { cap.restore(); } }); test("rejects UUID-shape accountId that does not match the expected one", async () => { const stranger = "ffffffff-1111-2222-3333-444444444444"; const cap = captureStderr(); try { await assert.rejects( () => writeNodeWithEdges({ // eslint-disable-next-line @typescript-eslint/no-explicit-any session: makeStubSession(TARGET_ID) as any, labels: ["Conversation"], props: { accountId: stranger }, relationships: REL, createdBy: { agent: "test-agent", session: "s" }, expectedAccountId: VALID_UUID, }), /invalid-account-id/, ); cap.restore(); assert.match( cap.output(), /\[graph-write\] reject reason=invalid-account-id accountId=ffffffff writer=test-agent\b/, ); } finally { cap.restore(); } }); test("accepts matching accountId from non-system writer", async () => { const result = await writeNodeWithEdges({ // eslint-disable-next-line @typescript-eslint/no-explicit-any session: makeStubSession(TARGET_ID) as any, labels: ["Conversation"], props: { accountId: VALID_UUID }, relationships: REL, createdBy: { agent: "test-agent", session: "s" }, expectedAccountId: VALID_UUID, }); assert.equal(result.nodeId, "new-element-id"); assert.equal(result.edgesCreated, 1); }); test("system bootstrap exemption: bad accountId still proceeds", async () => { const result = await writeNodeWithEdges({ // eslint-disable-next-line @typescript-eslint/no-explicit-any session: makeStubSession(TARGET_ID) as any, labels: ["Conversation"], // accountId is intentionally wrong; system bootstrap exempts it. props: { accountId: "not-a-uuid" }, relationships: REL, createdBy: { agent: "system", source: "writeAdminUserAndPerson" }, expectedAccountId: VALID_UUID, }); assert.equal(result.nodeId, "new-element-id"); });