// Row 171: diagnostic file reads stay in fixture config; project creation uses a real broker. import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { hostDir, hostFile, startTimeOf } from '../src/host.mjs'; import { join } from 'node:path'; import { run } from '../src/cli.mjs'; import { broker, BUSINESSES, fixture, io, tmp } from './helpers.mjs'; function setup(t) { const root = tmp(t), config = fixture(root, 'demo'); const s = broker(t, { demo: { ...BUSINESSES.demo, projects: { stack: {} } } }); const reads = []; const transport = business => async (verb, args) => { reads.push(verb); if (verb === 'plugins') return { business, revision: 'a'.repeat(64), plugins: { channel: ['cli', 'webui'] } }; return s.transport(business)(verb, args); }; const cli = async (args, explicit = true) => { const x = io(config.env); const code = await run([...args, ...(explicit ? ['--business', 'demo'] : [])], x, { system: { dataRoot: config.dataRoot }, transport }); return { code, out: x.out.text, err: x.err.text }; }; return { ...s, root, config, reads, cli }; } test('threads project resolution explains a business repository and falls back if its file cannot load', async t => { const s = setup(t); const args = ['threads', '--project', 'stack']; assert.deepEqual(await s.cli(args), { code: 2, out: '', err: 'mosaic: "stack" is a repository in the business file, not a thread project yet; mosaic project create stack --repo stack makes one\n', }); assert.deepEqual(s.reads, ['plugins', 'project.list']); // A stand-in host state names this test process; no host or unit is started. mkdirSync(hostDir(s.config.dataRoot), { recursive: true, mode: 0o700 }); writeFileSync(hostFile(s.config.dataRoot), JSON.stringify({ pid: process.pid, startTime: startTimeOf(process.pid), business: 'demo' }), { mode: 0o600 }); s.reads.length = 0; const implicit = await s.cli(args, false); assert.equal(implicit.code, 2); assert.equal(implicit.err, 'mosaic: "stack" is a repository in the business file, not a thread project yet; mosaic project create stack --repo stack makes one\n'); assert.deepEqual(s.reads, ['plugins', 'project.list']); const existing = setup(t); assert.equal((await existing.cli(['project', 'create', 'stack', '--repo', 'stack'])).code, 0); writeFileSync(join(existing.root, 'config/businesses/demo.json'), '{broken json'); assert.deepEqual(await existing.cli(args), { code: 0, out: 'no threads\n', err: '' }); assert.deepEqual(await existing.cli(['threads', '--project', 'other']), { code: 2, out: '', err: 'mosaic: no project is named or numbered "other"; see mosaic project list\n', }); s.reads.length = 0; rmSync(join(s.root, 'config/businesses/demo.json')); assert.deepEqual(await s.cli(args), { code: 2, out: '', err: 'mosaic: no project is named or numbered "stack"; see mosaic project list\n', }); assert.deepEqual(s.reads, ['plugins', 'project.list']); }); test('project create prints the repository-name refusal and exits two without writing', async t => { const s = setup(t); assert.deepEqual(await s.cli(['project', 'create', 'stack']), { code: 2, out: '', err: 'mosaic: "stack" is a repository in the business file; add --repo stack to make its thread project, or pick another name\n', }); assert.deepEqual(s.b.request(s.human, { verb: 'project.list' }), []); assert.equal((await s.cli(['project', 'create', 'stack', '--repo', 'stack'])).code, 0); });