// Row 155 (#1649, Q169): the Console's seat name and assistant stop routes. Each has the Settings // boundary and makes one human-transport call; the broker's own checks are in the bus tests. import test from 'node:test'; import assert from 'node:assert/strict'; import { request } from 'node:http'; import { startServer } from '../src/serve.mjs'; import { BusReadError } from '../src/bus.mjs'; import { createSsoSession } from './helpers/sso-session.mjs'; async function setup(t, { on = true, name = async args => ({ ...args }), stop = async () => ({ stopped: [{ thread: 't-1', run: 'a-run' }] }), bus = null } = {}) { const calls = [], lines = []; const auth = await createSsoSession(t, { host: 'console.example' }); const server = await startServer({ port: 0, sso: auth.sso, bus, log: line => lines.push(line), send: on ? async () => ({}) : null, nameWrite: on ? async args => { calls.push(['name.set', args]); return name(args); } : null, assistantStop: on ? async args => { calls.push(['assistant.stop', args]); return stop(args); } : null, }); t.after(() => new Promise(r => server.close(r))); const session = await auth.signIn(server.address().port); return { calls, lines, port: server.address().port, headers: auth.writeHeaders(session) }; } const send = (s, path, data, hs = s.headers, method = 'POST') => new Promise((resolve, reject) => { const req = request({ hostname: '127.0.0.1', port: s.port, path, method, headers: hs }, r => { let text = ''; r.on('data', c => text += c); r.on('end', () => resolve({ status: r.statusCode, body: text ? JSON.parse(text) : null })); }); req.on('error', reject); req.end(data === undefined ? undefined : typeof data === 'string' ? data : JSON.stringify(data)); }); test('Q169 the signed-in human names a seat and stops the assistant, one bus call each, logged with the user', async t => { const s = await setup(t); const named = await send(s, '/api/name', { instance: 'assistant', name: 'piper' }); assert.equal(named.status, 200); assert.equal(named.body.name, 'piper'); const stopped = await send(s, '/api/assistant-stop', { instance: 'assistant' }); assert.equal(stopped.status, 200); assert.deepEqual(stopped.body.stopped, [{ thread: 't-1', run: 'a-run' }]); assert.deepEqual(s.calls, [['name.set', { instance: 'assistant', name: 'piper' }], ['assistant.stop', { instance: 'assistant' }]]); assert.match(s.lines[0], / name user="jason" instance="assistant" done$/); assert.match(s.lines[1], / assistant-stop user="jason" instance="assistant" done$/); }); test('Q169 the name and stop routes need send on, the public name, the user, the exact Origin and same-origin Fetch Metadata', async t => { const off = await setup(t, { on: false }); for (const path of ['/api/name', '/api/assistant-stop']) assert.equal((await send(off, path, { instance: 'assistant' })).status, 403); const s = await setup(t); for (const path of ['/api/name', '/api/assistant-stop']) { const data = path === '/api/name' ? { instance: 'assistant', name: 'piper' } : { instance: 'assistant' }; for (const [key, value, status] of [['host', 'wrong.example', 403], ['cookie', '__Host-mosaic_session=invalid', 401], ['cookie', undefined, 401], ['origin', 'https://else.example', 403], ['origin', undefined, 403], ['sec-fetch-site', 'cross-site', 403], ['sec-fetch-site', undefined, 403]]) { const hs = { ...s.headers }; if (value === undefined) delete hs[key]; else hs[key] = value; assert.equal((await send(s, path, data, hs)).status, status, `${path} ${key}`); } assert.equal((await send(s, path, undefined, s.headers, 'GET')).status, 405); } assert.equal(s.calls.length, 0); }); test('Q169 the name and stop routes take JSON within 4 KiB and only their own keys, all text', async t => { const s = await setup(t); for (const value of [{ instance: 'assistant' }, { instance: 'assistant', name: 'piper', by: 'jason' }, { instance: 'assistant', name: 7 }, [], 'bad', { instance: 'assistant', name: 'x'.repeat(5000) }]) assert.equal((await send(s, '/api/name', value)).status, 400, JSON.stringify(value).slice(0, 40)); for (const value of [{}, { instance: 'assistant', thread: 't-1' }, { instance: null }]) assert.equal((await send(s, '/api/assistant-stop', value)).status, 400, JSON.stringify(value)); assert.equal((await send(s, '/api/name', { instance: 'assistant', name: 'piper' }, { ...s.headers, 'content-type': 'text/plain' })).status, 400); assert.equal(s.calls.length, 0); }); // Row 167 (#1664, Dewey's F1): a JSON null body is refused by body() before the key check runs, // so neither route throws on it, calls the bus or stops answering. test('Row 167 a null body on the name and stop routes is 400 invalid-request with no write, and the next request is answered', async t => { const s = await setup(t); for (const [path, data, call] of [['/api/name', { instance: 'assistant', name: 'piper' }, 'name.set'], ['/api/assistant-stop', { instance: 'assistant' }, 'assistant.stop']]) { const before = s.calls.length; const r = await send(s, path, 'null'); assert.deepEqual([r.status, r.body.error, r.body.sent], [400, 'invalid-request', false], path); assert.equal(s.calls.length, before, path); assert.equal((await send(s, path, data)).status, 200, path); assert.deepEqual(s.calls.at(-1), [call, data]); } assert.equal(s.calls.length, 2); }); test('Q169 a bus refusal says nothing changed; a lost or odd answer says the change may have been made', async t => { const fail = code => async () => { throw new BusReadError(code); }; for (const [stop, status, error, sent] of [ [fail('not-held'), 409, 'not-held', false], [fail('human-required'), 403, 'human-required', false], [fail('not-assistant'), 400, 'not-assistant', false], [fail('outcome-unknown'), 503, 'outcome-unknown', 'unknown'], [async () => ({ stopped: 'all' }), 503, 'outcome-unknown', 'unknown'], ]) { const s = await setup(t, { stop }); const r = await send(s, '/api/assistant-stop', { instance: 'assistant' }); assert.deepEqual([r.status, r.body.error, r.body.sent], [status, error, sent]); assert.equal(s.calls.length, 1); assert.match(s.lines[0], new RegExp(` error=${error}$`)); } const s = await setup(t, { name: fail('name-taken') }); const r = await send(s, '/api/name', { instance: 'assistant', name: 'piper' }); assert.deepEqual([r.status, r.body.error, r.body.sent], [409, 'name-taken', false]); }); test('Q169 the names read is a bus read like the others', async t => { const rows = [{ instance: 'assistant', name: 'piper', at: '2026-10-18T00:00:00.000Z' }]; const s = await setup(t, { bus: { names: async () => rows } }); const r = await send(s, '/api/bus/names', undefined, s.headers, 'GET'); assert.equal(r.status, 200); assert.deepEqual(r.body.rows, rows); });