// C2-14 S10: actual Console HTTP chat endpoints and browser DOM over the same acceptance bus. // The unrelated Board is a local 503 fixture. No chat response, citation check or notice is stubbed. import { createServer } from 'node:http'; import { startServer } from '../../packages/webui/src/serve.mjs'; import { consoleBus } from '../../packages/webui/src/console-bus.mjs'; import { browser } from '../../packages/webui/tests/browser.mjs'; const shutdown = server => new Promise((resolve, reject) => { server.close(error => error ? reject(error) : resolve()); server.closeAllConnections?.(); }); const expect = (value, label) => { if (!value) throw new Error(label); }; export async function consoleAcceptance({ humanCall, thread, reply, writeNotice, writeCommit, cleanups }) { const board = createServer((req, res) => { res.writeHead(503); res.end('Acceptance Board is not configured.'); }); await new Promise(resolve => board.listen(0, '127.0.0.1', resolve)); cleanups.push(() => shutdown(board)); const options = consoleBus(humanCall); const server = await startServer({ port: 0, board: `http://127.0.0.1:${board.address().port}`, ...options, log: () => {} }); cleanups.push(() => shutdown(server)); const origin = `http://127.0.0.1:${server.address().port}`; const response = await fetch(`${origin}/api/chat/threads/${thread}/messages`); expect(response.ok, 'Console message endpoint refused'); const body = await response.json(); expect(Array.isArray(body.rows), 'Console message shape'); const checked = body.rows.find(m => m.id === reply.id), notice = body.rows.find(m => m.id === writeNotice.id); expect(checked?.body === reply.body && checked.check?.citations?.[0]?.scope === 'canon', 'Console dropped stored reply/check'); expect(notice?.body === writeNotice.body, 'Console dropped broker notice'); const view = await browser({ machine: false }); cleanups.push(() => view.close()); await view.navigate(`${origin}/#/chat/threads/${thread}`); const rendered = await view.evaluate(`(async () => { for (let i = 0; i < 150; i++) { const bodies = [...document.querySelectorAll('.chat-log .chat-body')].map(e => e.textContent); const notices = [...document.querySelectorAll('.chat-notice > span:first-child')].map(e => e.textContent); const canon = [...document.querySelectorAll('.chat-cites')].some(e => e.textContent.includes('canon')); if (bodies.some(t => t === 'Grey eyes.') && canon && notices.some(t => t === ${JSON.stringify(`bean wrote ${JSON.parse(writeNotice.body).path} @${writeCommit.slice(0, 7)}`)})) return { reply: true, canon, notice: true }; await new Promise(resolve => setTimeout(resolve, 40)); } return { reply: false, canon: false, notice: false }; })()`); expect(rendered.reply && rendered.canon && rendered.notice, 'Console did not render actual checked reply and write notice'); }