import test from 'node:test'; import assert from 'node:assert/strict'; import { request } from 'node:http'; import { mkdtempSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { startServer } from '../src/serve.mjs'; import { startTesterSocket, TESTER_ORIGIN } from '../src/tester-socket.mjs'; import { close } from './fixture.mjs'; const path = '/api/chat/instances'; const preview = { host: 'chat.example', header: 'x-user', user: 'jason' }; const snapshot = { revision: 'a'.repeat(64), plugins: { private: 'do-not-project' }, instances: { assistant: { definition: 'assistant', vars: { model: 'private-model' } }, bean: { definition: 'companion', vars: { 'persona.name': 'Bean', 'persona.sha256': 'private-hash' } }, chet: { definition: 'continuity', vars: { 'persona.name': 'Bean', file: '/private/path' } }, pm: { definition: 'pm', vars: { 'persona.name': 'Hidden PM' } }, }, }; function get(server, { route = path, method = 'GET', headers = {} } = {}) { const address = server.address(), unix = typeof address === 'string'; return new Promise((resolve, reject) => { const req = request({ ...(unix ? { socketPath: address } : { host: '127.0.0.1', port: address.port }), path: route, method, headers: { host: unix ? new URL(TESTER_ORIGIN).host : preview.host, ...headers } }, res => { let text = ''; res.on('data', chunk => text += chunk); res.on('end', () => resolve({ status: res.statusCode, text, value: JSON.parse(text) })); }); req.on('error', reject); req.end(); }); } test('C2-09a metadata uses the human listener bus, projects labels only and refuses unauthenticated reads', async t => { let calls = 0; const server = await startServer({ port: 0, preview, bus: { instances: async () => { calls++; return snapshot; } } }); t.after(() => close(server)); const refused = await get(server); assert.equal(refused.status, 401); assert.equal(calls, 0); const answer = await get(server, { headers: { 'x-user': 'jason' } }); assert.equal(answer.status, 200); assert.deepEqual(answer.value.rows, [{ instance: 'assistant', name: 'assistant' }, { instance: 'bean', name: 'Bean' }, { instance: 'chet', name: 'Bean' }]); for (const row of answer.value.rows) assert.deepEqual(Object.keys(row).sort(), ['instance', 'name']); assert.doesNotMatch(answer.text, /private|Hidden PM/); assert.equal(calls, 1); for (const options of [{ route: path + '?extra=1' }, { method: 'POST' }]) { const result = await get(server, { ...options, headers: { 'x-user': 'jason' } }); assert.ok([400, 405].includes(result.status)); } assert.equal(calls, 1, 'invalid metadata requests never call a bus'); }); test('C2-09a metadata uses the private tester listener own bus and no human transport', async t => { const root = mkdtempSync(join(tmpdir(), 'chat219-')); t.after(() => rmSync(root, { recursive: true, force: true })); let reads = 0; const server = await startTesterSocket({ dataRoot: root, board: 'http://127.0.0.1:9', bus: { instances: async () => { reads++; return { instances: { quill: { definition: 'proofreader', vars: { 'persona.name': 'Quill' } } } }; } } }); t.after(() => close(server)); const answer = await get(server); assert.equal(answer.status, 200); assert.deepEqual(answer.value.rows, [{ instance: 'quill', name: 'Quill' }]); assert.equal(reads, 1); }); test('C2-09a metadata refuses missing or malformed reads and never echoes untrusted fields', async t => { for (const bus of [null, { instances: async () => ({ instances: [] }) }, { instances: async () => { throw new Error('private-path'); } }]) { const server = await startServer({ port: 0, preview, bus }); t.after(() => close(server)); const answer = await get(server, { headers: { 'x-user': 'jason' } }); assert.ok(answer.status >= 400); assert.doesNotMatch(answer.text, /private-path/); } }); test('C2-09a metadata stays behind the SSO session check and a GET never refreshes session activity', async t => { let session = null, reads = 0; const touches = []; const sso = { publicHost: 'sso-chat.example', publicOrigin: 'https://sso-chat.example', formTargets: () => [], session: (_req, { touch }) => { touches.push(touch); return session; } }; const server = await startServer({ port: 0, sso, bus: { instances: async () => { reads++; return snapshot; } } }); t.after(() => close(server)); const headers = { host: sso.publicHost }; assert.equal((await get(server, { headers })).status, 401); assert.equal(reads, 0); session = { name: 'jason', rootOwner: true }; assert.equal((await get(server, { headers })).status, 200); assert.equal(reads, 1); assert.deepEqual(touches, [false, false]); });