// Row 176: full fixture config, credential sentinels and Git project; never the user's config. import { mkdirSync, readFileSync, writeFileSync, symlinkSync } from 'node:fs'; import { join } from 'node:path'; import { execFileSync } from 'node:child_process'; import { DatabaseSync } from 'node:sqlite'; import { fixture, tmp } from '../../cli/tests/helpers.mjs'; import { loadSystem, bootConfig } from '../../cli/src/config.mjs'; import { loadBusiness } from '../../organization/src/index.mjs'; import { writeJson } from '../../organization/tests/helpers.mjs'; import { initProject } from '../../project/src/index.mjs'; import { startBroker } from '../../bus/src/runtime.mjs'; import { Broker, BusError } from '../../bus/src/broker.mjs'; import { Store } from '../../bus/src/store.mjs'; import { BusReadError } from '../src/bus.mjs'; import { views } from '../../bus/src/views.mjs'; import { startTesterSocket } from '../src/tester-socket.mjs'; import { createChat } from '../src/chat.mjs'; import { close } from './fixture.mjs'; export const TOKEN = 'row176-fixture-token-sentinel'; export const VARIABLE = 'ROW176_FIXTURE_CREDENTIAL_VARIABLE'; export const OUTSIDE = 'row176-outside-target-must-never-be-read'; export async function testerFixture(t, { enabled = true } = {}) { const root = tmp(t, 'console-tester-'); const f = fixture(root, 'acme', doc => { doc.vars['bus.tester'] = enabled; doc.projects.stack.repo = join(root, 'records'); return doc; }); const file = join(root, 'config/businesses/acme.json'); const tokenPath = f.doc.roles.pm.credentials.gitea.file; writeFileSync(tokenPath, TOKEN, { mode: 0o600 }); const old = process.env[VARIABLE]; process.env[VARIABLE] = TOKEN; t.after(() => { if (old === undefined) delete process.env[VARIABLE]; else process.env[VARIABLE] = old; }); f.doc.roles.coder.credentials.gitea = { env: VARIABLE, rotateBy: '2099-01-01' }; writeJson(file, f.doc); const business = loadBusiness('acme', { dir: join(root, 'config'), rolesDir: f.roles }); await initProject({ business, project: 'stack', dataRoot: f.dataRoot }); writeFileSync(join(root, 'outside.md'), OUTSIDE); symlinkSync(join(root, 'outside.md'), join(root, 'records/docs/out.md')); execFileSync('git', ['-c', 'core.hooksPath=/dev/null', 'add', 'docs/out.md'], { cwd: join(root, 'records') }); execFileSync('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'maintenance.auto=false', '-c', 'gc.auto=0', 'commit', '-m', 'Fixture symlink'], { cwd: join(root, 'records'), env: { ...process.env, GIT_AUTHOR_NAME: 'jason', GIT_AUTHOR_EMAIL: 'fixture@invalid', GIT_COMMITTER_NAME: 'jason', GIT_COMMITTER_EMAIL: 'fixture@invalid' }, }); mkdirSync(f.dataRoot, { recursive: true, mode: 0o700 }); const runtime = await startBroker(bootConfig({ system: loadSystem({ env: f.env }), businessId: 'acme', env: f.env })); t.after(() => runtime.close()); const calls = []; const call = async (verb, args = {}) => { calls.push([verb, args]); try { return structuredClone(await runtime.broker.request(runtime.broker.bindTester({ business: 'acme' }), { verb, args })); } catch (err) { if (err instanceof BusError) throw new BusReadError(err.code); throw err; } }; const bus = { ...views({ call }), names: () => call('names'), instances: () => call('instances'), routes: () => call('routes') }; const lines = []; const web = await startTesterSocket({ dataRoot: f.dataRoot, board: 'http://127.0.0.1:9', tester: true, testerAuthHeader: 'x-user', bus, reads: { settings: async () => ({ business: 'acme', release: 'fixture', notifier: {}, launch: await call('limits') }) }, projectFiles: args => call('project.files', args), projectFile: args => call('project.file', args), instancesWrite: args => call('instances.set', args), routesWrite: args => call('routes.set', args), nameWrite: args => call('name.set', args), log: line => lines.push(line), }); t.after(() => close(web)); const history = () => readFileSync(join(root, 'config/businesses/.history/acme/changes.jsonl'), 'utf8').trim().split('\n').map(JSON.parse); const nameRows = () => { const db = new DatabaseSync(join(f.dataRoot, 'bus/bus.sqlite'), { readOnly: true }); try { return db.prepare('SELECT name,by FROM instance_names').all(); } finally { db.close(); } }; return { root, f, file, tokenPath, runtime, calls, call, web, lines, history, nameRows }; } // Row 177 (#1675): a tester listener whose chat calls a plain broker as the tester. The shared // fixture has no assistant role, and other tests count its roles, so the chat gets its own business. export async function testerChat(t, { enabled = true } = {}) { const root = tmp(t, 'console-tester-chat-'); const store = new Store(root); t.after(() => store.close()); const none = { authority: { withinRole: ['message.send'], crossRole: [] } }; const broker = new Broker({ store, businesses: { demo: { id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' }, vars: { 'bus.tester': enabled }, roles: { assistant: { definition: 'assistant', ...none }, chief: { definition: 'chief-of-staff', ...none } }, definitions: { assistant: {}, 'chief-of-staff': {} } } } }); const human = (verb, args = {}) => broker.request(broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }), { verb, args }); const calls = []; const chat = createChat({ call: async (verb, args) => { calls.push(verb); try { // Bound at each call, as the tester CLI is, so bus.tester off refuses before any read. const tester = broker.bindTester({ business: 'demo' }); if (verb === 'plugins') return { business: 'demo', revision: 'a'.repeat(64), plugins: { channel: ['cli', 'webui'] } }; return structuredClone(broker.request(tester, { verb, args })); } catch (err) { if (err instanceof BusError) throw new BusReadError(err.code); throw err; } } }); const lines = []; const web = await startTesterSocket({ dataRoot: root, board: 'http://127.0.0.1:9', tester: true, testerAuthHeader: 'x-user', chat, log: line => lines.push(line) }); t.after(() => close(web)); return { store, broker, human, calls, web, lines }; }