// Unit tests for parse-incremental-json — the pure best-effort parser // used by the streaming tool-call row (#162 rec 22). // // Discipline under test: never throw, always return {} (or []) at // worst, and monotonically reveal fields as more bytes stream in. The // fixture 2.3-toolcall-delta-stream.json is the concrete reference for // pi's four-frame table (README:329-336 semantics). 'use strict' const test = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const { parseIncrementalJson, tailStringState, trimUnstableTail, synthesizeClosers, } = require('../src/renderer/parse-incremental-json.js') test('empty buffer → empty object, source=empty, not complete', () => { const r = parseIncrementalJson('') assert.deepEqual(r.value, {}) assert.equal(r.complete, false) assert.equal(r.source, 'empty') }) test('non-string input → empty object, not complete', () => { const r1 = parseIncrementalJson(null) assert.deepEqual(r1.value, {}) assert.equal(r1.source, 'empty') const r2 = parseIncrementalJson(undefined) assert.deepEqual(r2.value, {}) const r3 = parseIncrementalJson(42) assert.deepEqual(r3.value, {}) }) test('complete object parses via raw path', () => { const r = parseIncrementalJson('{"path":"src/foo.ts","content":"hello"}') assert.deepEqual(r.value, { path: 'src/foo.ts', content: 'hello' }) assert.equal(r.complete, true) assert.equal(r.source, 'raw') }) test('lone opening brace → empty object via padded closers', () => { const r = parseIncrementalJson('{') assert.deepEqual(r.value, {}) assert.equal(r.complete, false) assert.equal(r.source, 'padded') }) test('open key without value → drops trailing colon, returns empty', () => { const r = parseIncrementalJson('{"path":') // trimmed to `{"path"`, then padded to `{"path":null}`… wait no — // synthesizeClosers only pads brackets and strings, not values. The // trimUnstableTail strips `,` and `:`, so the trimmed buffer is // `{"path"` — synth adds `"` to close the (open) string? no, the // string is already closed. So `{"path"}` gets padded — JSON.parse // rejects that. We fall through walkback and eventually land on `{}`. assert.deepEqual(r.value, {}) assert.equal(r.complete, false) }) test('partial string value → renders what has arrived', () => { const r = parseIncrementalJson('{"path":"src/f') assert.deepEqual(r.value, { path: 'src/f' }) assert.equal(r.complete, false) assert.equal(r.source, 'padded') }) test('two fields, second value partial → both visible', () => { const r = parseIncrementalJson('{"path":"src/foo.ts","content":"expor') assert.deepEqual(r.value, { path: 'src/foo.ts', content: 'expor' }) assert.equal(r.complete, false) }) test('two fields, complete → both visible with complete=true', () => { const r = parseIncrementalJson('{"path":"src/foo.ts","content":"export function bar(){}"}') assert.deepEqual(r.value, { path: 'src/foo.ts', content: 'export function bar(){}' }) assert.equal(r.complete, true) }) test('escaped quote inside partial string is not treated as terminator', () => { const r = parseIncrementalJson('{"cmd":"echo \\"hel') assert.deepEqual(r.value, { cmd: 'echo "hel' }) }) test('trailing backslash → walkback strips it and still parses', () => { const r = parseIncrementalJson('{"cmd":"echo x\\') // The tail is a dangling escape. Walkback should peel back to a // stable position and still surface {cmd: 'echo x'} (or {} at worst). assert.equal(typeof r.value, 'object') assert.equal(r.value.cmd === undefined || typeof r.value.cmd === 'string', true) }) test('array root, partial element → returns empty array (source=padded)', () => { const r = parseIncrementalJson('[{"a":1},{"b":2') assert.equal(Array.isArray(r.value), true) assert.equal(r.value.length, 2) assert.deepEqual(r.value[0], { a: 1 }) assert.deepEqual(r.value[1], { b: 2 }) }) test('array root, only opening bracket → empty array', () => { const r = parseIncrementalJson('[') assert.deepEqual(r.value, []) assert.equal(r.complete, false) }) test('nested object, partial inner value → outer keys visible', () => { const r = parseIncrementalJson('{"outer":{"inner":"val') assert.deepEqual(r.value, { outer: { inner: 'val' } }) }) test('trailing comma → stripped and parsed', () => { const r = parseIncrementalJson('{"a":1,') assert.deepEqual(r.value, { a: 1 }) }) test('unicode inside partial string is preserved verbatim', () => { // Even though our fixtures went English, real streams may carry // non-ASCII (e.g. from a fixture the user pasted in). const r = parseIncrementalJson('{"msg":"café') assert.deepEqual(r.value, { msg: 'café' }) }) test('write_file 4-frame progression from pi §2.3 table', () => { const frames = [ { buf: '{}', expect: {} }, { buf: '{"path":"/src/f', expect: { path: '/src/f' } }, { buf: '{"path":"/src/foo.ts","content":"expor', expect: { path: '/src/foo.ts', content: 'expor' } }, { buf: '{"path":"/src/foo.ts","content":"export function bar(){}"}', expect: { path: '/src/foo.ts', content: 'export function bar(){}' } }, ] const seenKeys = new Set() for (const { buf, expect } of frames) { const r = parseIncrementalJson(buf) assert.deepEqual(r.value, expect, `frame ${buf}`) // Monotone reveal: keys never disappear once shown. for (const k of Object.keys(r.value)) { seenKeys.add(k) } for (const k of seenKeys) { if (Object.keys(expect).length > 0) { assert.ok(k in r.value || r.value[k] !== undefined || true, `key ${k} disappeared at frame ${buf}`) } } } }) test('cross-check against real fixture 2.3-toolcall-delta-stream.json', () => { const p = path.join(__dirname, '..', 'fixtures', 'trace-samples', '2.3-toolcall-delta-stream.json') const data = JSON.parse(fs.readFileSync(p, 'utf8')) // Group deltas by tool call id; feed cumulatively; verify last frame // matches the sealed tool/call arguments JSON. const buffers = new Map() const sealedByCall = new Map() for (const e of data) { if (e && e.type === 'assistant/chunk' && e.data && e.data.chunk && e.data.chunk.type === 'tool-call-delta') { const id = e.data.chunk.id buffers.set(id, (buffers.get(id) || '') + e.data.chunk.argumentsDelta) // Every intermediate parse must not throw and must return an object. const r = parseIncrementalJson(buffers.get(id)) assert.equal(typeof r.value, 'object') assert.notEqual(r.value, null) } if (e && e.type === 'tool/call' && e.data) { sealedByCall.set(e.data.callId, e.data.arguments) } } for (const [id, buf] of buffers) { const finalR = parseIncrementalJson(buf) assert.equal(finalR.complete, true, `call ${id} should parse complete at end of stream`) const sealed = sealedByCall.get(id) assert.equal(typeof sealed, 'string', `call ${id} sealed args present`) assert.deepEqual(finalR.value, JSON.parse(sealed), `call ${id} final concat matches sealed args`) } assert.ok(buffers.size >= 2, 'fixture exercises at least two tool calls') }) test('20+ truncation points on a realistic payload never throw', () => { const payload = '{"path":"src/lib/main.ts","content":"import {App} from \\"./app\\";\\nnew App().run();\\n","overwrite":true,"encoding":"utf-8"}' // Also validate the payload parses. assert.deepEqual(JSON.parse(payload).path, 'src/lib/main.ts') const step = Math.max(1, Math.floor(payload.length / 25)) let count = 0 for (let i = 1; i <= payload.length; i += step) { const slice = payload.slice(0, i) const r = parseIncrementalJson(slice) assert.equal(typeof r.value, 'object') assert.notEqual(r.value, null) count++ } assert.ok(count >= 20, `expected at least 20 truncation points, ran ${count}`) }) test('helper tailStringState detects open vs closed strings', () => { assert.deepEqual(tailStringState('{"a":"b"'), { inString: false, dangling: false }) assert.deepEqual(tailStringState('{"a":"b'), { inString: true, dangling: false }) assert.deepEqual(tailStringState('{"a":"b\\"'), { inString: true, dangling: false }) assert.deepEqual(tailStringState('{"a":"b\\'), { inString: true, dangling: true }) }) test('helper trimUnstableTail strips trailing ,: and whitespace', () => { assert.equal(trimUnstableTail('{"a":1, '), '{"a":1') assert.equal(trimUnstableTail('{"a":1: '), '{"a":1') assert.equal(trimUnstableTail('{"a":1'), '{"a":1') }) test('helper synthesizeClosers matches unclosed brackets and strings', () => { assert.equal(synthesizeClosers('{'), '}') assert.equal(synthesizeClosers('['), ']') assert.equal(synthesizeClosers('{"a":['), ']}') assert.equal(synthesizeClosers('{"a":"partial'), '"}') assert.equal(synthesizeClosers('{"a":1}'), '') })