// Neo4j envelope-warning passthrough. // // Pure-function tests for the helpers in cypher-shim-read.ts: // - filterEnvelopeNotifications: keeps only ^0[12]N5\d$ GQL status codes // - stitchWarningsIntoResponse: prepends a warnings content block // - runReadProbe: runs cypher through a shim-side session // and returns notifications from the summary // // Failure mode this closes: the agent ran `RETURN h.hostname` against a node // whose property is `hostnameValue`, Neo4j emitted gql_status=01N52 // "property does not exist", but the warning was dropped by the upstream // envelope and the agent saw `[]`. Without these warnings reaching the tool // result, the same recurrence will hit other property names too. import test from "node:test"; import assert from "node:assert/strict"; import { filterEnvelopeNotifications, stitchWarningsIntoResponse, runReadProbe, type DriverNotification, } from "../cypher-shim-read.js"; test("filterEnvelopeNotifications keeps 01N5x and 02N5x, drops everything else", () => { const input: DriverNotification[] = [ { gqlStatus: "01N52", title: "...", description: "property does not exist (foo)", position: { offset: 0, line: 1, column: 1 } }, { gqlStatus: "02N50", title: "...", description: "label does not exist (Foo)", position: null }, { gqlStatus: "01N40", title: "...", description: "wrong family", position: null }, { gqlStatus: "03N42", title: "...", description: "unrelated", position: null }, { gqlStatus: "00N00", title: "...", description: "neutral status", position: null }, ]; const filtered = filterEnvelopeNotifications(input); assert.equal(filtered.length, 2); assert.equal(filtered[0].gql_status, "01N52"); assert.equal(filtered[0].description, "property does not exist (foo)"); assert.deepEqual(filtered[0].position, { offset: 0, line: 1, column: 1 }); assert.equal(filtered[1].gql_status, "02N50"); assert.equal(filtered[1].position, null); }); test("filterEnvelopeNotifications on an empty or all-irrelevant list returns []", () => { assert.deepEqual(filterEnvelopeNotifications([]), []); assert.deepEqual( filterEnvelopeNotifications([ { gqlStatus: "03N42", title: "", description: "", position: null }, { gqlStatus: "01N40", title: "", description: "", position: null }, ]), [], ); }); test("stitchWarningsIntoResponse prepends a warnings content block when warnings exist", () => { const original = { jsonrpc: "2.0", id: 42, result: { content: [{ type: "text", text: "[]" }] }, }; const warnings = [ { gql_status: "01N52", description: "property hostname does not exist", position: null }, ]; const out = stitchWarningsIntoResponse(original, warnings); assert.notEqual(out, null); const parsed = JSON.parse(out!); assert.equal(parsed.id, 42); assert.equal(parsed.result.content.length, 2); assert.equal(parsed.result.content[0].type, "text"); assert.match(parsed.result.content[0].text, /Neo4j envelope warnings/); assert.match(parsed.result.content[0].text, /01N52/); assert.match(parsed.result.content[0].text, /property hostname does not exist/); // Original row block preserved verbatim, after the warnings prefix. assert.equal(parsed.result.content[1].text, "[]"); }); test("stitchWarningsIntoResponse returns null when no warnings (no envelope rewrite)", () => { const original = { jsonrpc: "2.0", id: 1, result: { content: [{ type: "text", text: "[]" }] } }; assert.equal(stitchWarningsIntoResponse(original, []), null); }); test("stitchWarningsIntoResponse handles missing result.content defensively", () => { const original = { jsonrpc: "2.0", id: 1, result: {} }; const warnings = [{ gql_status: "01N52", description: "x", position: null }]; const out = stitchWarningsIntoResponse(original as never, warnings); assert.notEqual(out, null); const parsed = JSON.parse(out!); assert.equal(parsed.result.content.length, 1); assert.match(parsed.result.content[0].text, /01N52/); }); test("runReadProbe returns notifications captured from the driver summary", async () => { const session = { run: async () => ({ summary: { notifications: [ { gqlStatus: "01N52", title: "", description: "missing prop", position: null }, ] as DriverNotification[], }, }), close: async () => {}, }; const driver = { session: () => session }; const notifs = await runReadProbe(driver, "MATCH (n) RETURN n.foo", {}); assert.equal(notifs.length, 1); assert.equal(notifs[0].gqlStatus, "01N52"); }); test("runReadProbe returns [] when driver summary has no notifications", async () => { const session = { run: async () => ({ summary: {} }), close: async () => {}, }; const driver = { session: () => session }; assert.deepEqual(await runReadProbe(driver, "RETURN 1", {}), []); }); test("runReadProbe closes the session even when run() throws", async () => { let closed = false; const session = { run: async () => { throw new Error("boom"); }, close: async () => { closed = true; }, }; const driver = { session: () => session }; await assert.rejects(() => runReadProbe(driver, "RETURN 1", {})); assert.equal(closed, true, "session must be closed on error"); }); test("end-to-end: filter + stitch on a real-shape upstream response surfaces 01N52", () => { // Models the failure mode in the original log: upstream returned an empty // row list; Neo4j attached an 01N52 notification that the upstream Python // server dropped. const upstreamResponse = { jsonrpc: "2.0", id: 99, result: { content: [{ type: "text", text: "[]" }] }, }; const driverNotifications: DriverNotification[] = [ { gqlStatus: "01N52", title: "Property does not exist", description: "The property 'hostname' does not exist on the node", position: { offset: 38, line: 1, column: 39 }, }, ]; const warnings = filterEnvelopeNotifications(driverNotifications); const stitched = stitchWarningsIntoResponse(upstreamResponse, warnings); const parsed = JSON.parse(stitched!); assert.match(parsed.result.content[0].text, /01N52/); assert.match(parsed.result.content[0].text, /hostname/); });