confluence_web/agent/tests/framing.test.ts
Artur Mukhamadiev 38a8ca67f7 agent: pi runtime track handoff (contract revision 1)
Pinned pi SDK 0.85.1 bridge, Python supervisor, artifact exporter,
scripted backend peer, image checks and boundary checks under agent/**.
Review findings F1-F3 are recorded in docs/implementation/PI_AGENT_REVIEW.md.
2026-09-14 21:57:54 +03:00

86 lines
5.3 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { PassThrough, Writable } from 'node:stream';
import { NDJsonFrameParser, StreamMessageWriter, sanitizeError } from '../framing.js';
import { envelope } from '../validation.js';
async function parse(chunks: Buffer[], max = 1024) {
const input = new PassThrough(); const parser = new NDJsonFrameParser(max);
input.pipe(parser);
const result = (async () => { const frames = []; for await (const frame of parser) frames.push(frame); return frames; })();
for (const c of chunks) input.write(c); input.end();
return result;
}
test('fragmented and coalesced UTF-8 frames, escaping', async () => {
const a = { v: 1, type: 'collection_ready', id: 'b_1', reply_to: 'a_1', payload: {} };
const b = { text: '雪 🐈 \\ " \n' };
const wire = Buffer.from(JSON.stringify(a) + '\n' + JSON.stringify(b) + '\n');
assert.deepEqual(await parse([...wire].map(x => Buffer.from([x]))), [a, b]);
assert.deepEqual(await parse([wire]), [a, b]);
});
test('invalid UTF-8, empty, malformed, oversized and truncated lines fail', async () => {
for (const bytes of [Buffer.from('\n'), Buffer.from('{\n'), Buffer.from('{"x":"\xff"}\n', 'latin1'), Buffer.alloc(65, 65), Buffer.from('{}')]) {
await assert.rejects(parse([bytes], 64));
}
assert.deepEqual(await parse([Buffer.from('{}\n')], 2), [{}]);
});
test('envelopes reject extra fields, wrong IDs and wrong versions', () => {
const a = { v: 1, type: 'collection_ready', id: 'b_1', reply_to: 'a_1', payload: {} };
envelope(a);
for (const b of [{ ...a, extra: true }, { ...a, v: 2 }, { ...a, id: 'a_1' }, { ...a, payload: { extra: true } }, { ...a, reply_to: 'invalid/path' }]) assert.throws(() => envelope(b));
});
test('serialized concurrent writes await slow callbacks and preserve order', async () => {
const lines: string[] = [];
const stream = new Writable({ highWaterMark: 1, write(chunk, _encoding, cb) { setTimeout(() => { lines.push(chunk.toString()); cb(); }, 5); } });
const writer = new StreamMessageWriter(stream);
await Promise.all(Array.from({ length: 8 }, (_, i) => writer.write({ v: 1, type: 'x', id: `a_${i}`, payload: {} })));
assert.deepEqual(lines.map(x => JSON.parse(x).id), Array.from({ length: 8 }, (_, i) => `a_${i}`));
});
test('terminal diagnostics never echo arbitrary exception content', () => {
assert(!JSON.stringify(sanitizeError('invalid_input', new Error('SECRET_PROMPT'))).includes('SECRET'));
assert.equal(sanitizeError('SECRET_CODE', 'SECRET_BODY').code, 'execution_failed');
});
test('fragment serializer preserves escaping and surrogate pairs across chunks', async () => {
const { jsonPieces, jsonBytes } = await import('../json.js');
const value = { text: 'x'.repeat(16383) + '🐈\u0001\n"\\', nested: [{ unicode: '雪' }, null, true, 3] };
const encoded = [...jsonPieces(value)].join('');
assert.equal(encoded, JSON.stringify(value));
assert.deepEqual(JSON.parse(encoded), value);
assert.equal(jsonBytes(value), Buffer.byteLength(encoded));
assert(jsonBytes(value, 32) > 32);
});
test('terminal write replaces backlog only after the active large NDJSON frame', async () => {
const chunks: string[] = []; let started!: () => void;
const first = new Promise<void>(resolve => started = resolve);
const stream = new Writable({ highWaterMark: 1, write(chunk, _encoding, cb) {
chunks.push(chunk.toString()); started(); setTimeout(cb, 8);
} });
const writer = new StreamMessageWriter(stream);
const large = { v: 1, type: 'collection_start', id: 'a_1', payload: { markdown: '雪'.repeat(100000), warnings: [] } };
const active = writer.write(large);
const queued = writer.write({ v: 1, type: 'artifact_begin', id: 'a_2', payload: {} });
const cancelled = assert.rejects(queued, /cancelled/);
await first;
const terminal = writer.writeTerminal({ v: 1, type: 'error', id: 'a_3', payload: sanitizeError('query_timeout') });
await Promise.all([active, cancelled, terminal, writer.close()]);
assert.deepEqual(chunks.join('').trimEnd().split('\n').map(line => JSON.parse(line)), [large, { v: 1, type: 'error', id: 'a_3', payload: sanitizeError('query_timeout') }]);
await assert.rejects(writer.write(large), /closed/);
});
test('a started terminal frame cannot be followed by another terminal message', async () => {
let release!: () => void; const chunks: string[] = [];
const stream = new Writable({ write(chunk, _encoding, cb) { chunks.push(chunk.toString()); release = () => cb(); } });
const writer = new StreamMessageWriter(stream);
const complete = writer.write({ v: 1, type: 'complete', id: 'a_1', payload: { accepted_transfer_count: 0 } });
await assert.rejects(writer.writeTerminal({ v: 1, type: 'error', id: 'a_2', payload: sanitizeError('query_timeout') }), /already started/);
release(); await complete; await writer.close();
assert.equal(chunks.join('').trimEnd().split('\n').length, 1);
});
test('broken output rejects active and queued frames and reports failed drain', async () => {
const stream = new Writable({ write(_chunk, _encoding, cb) { setTimeout(() => cb(new Error('DUMMY_PRIVATE_ERROR')), 5); } });
const writer = new StreamMessageWriter(stream);
await Promise.all([1, 2].map(i => assert.rejects(writer.write({ v: 1, type: 'x', id: `a_${i}`, payload: {} }), /Output stream failed/)));
await assert.rejects(writer.close(), /Output stream failed/);
});