From 1d4a94d66f90b1f4afab605ba966862d28dfc33f Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Fri, 17 Jul 2026 11:55:59 -0500 Subject: [PATCH] test(#792): cover roster guard and installer heading --- packages/mosaic/src/commands/fleet.spec.ts | 50 +++++++++++++++++++ .../src/commands/install-heading.spec.ts | 17 +++++++ 2 files changed, 67 insertions(+) create mode 100644 packages/mosaic/src/commands/install-heading.spec.ts diff --git a/packages/mosaic/src/commands/fleet.spec.ts b/packages/mosaic/src/commands/fleet.spec.ts index e54cc0d..65cf7c8 100644 --- a/packages/mosaic/src/commands/fleet.spec.ts +++ b/packages/mosaic/src/commands/fleet.spec.ts @@ -60,6 +60,7 @@ import { type SleepFn, } from './fleet.js'; import { registerAgentCommand } from './agent.js'; +import { parseFleetRosterDocument, readFleetRosterText } from '../fleet/fleet-roster-v1.js'; function buildProgram(): Command { const program = new Command(); @@ -166,6 +167,55 @@ describe('registerFleetCommand', () => { }); }); +describe('fleet roster configuration failures', () => { + it('reports a missing roster with an actionable initialization hint', async () => { + const home = await tempDir(); + try { + const program = buildProgram(); + + await expect( + program.parseAsync(['node', 'mosaic', 'fleet', '--mosaic-home', home, 'ps']), + ).rejects.toThrow( + `No fleet roster found at ${join(home, 'fleet', 'roster.json')}. Run \`mosaic fleet init\``, + ); + } finally { + await rm(home, { recursive: true, force: true }); + } + }); + + it('reports a malformed roster with the path and repair hint', async () => { + const home = await tempDir(); + const rosterPath = join(home, 'fleet', 'roster.json'); + try { + await mkdir(dirname(rosterPath), { recursive: true }); + await writeFile(rosterPath, '{not valid json'); + + await expect(loadFleetRoster(rosterPath)).rejects.toThrow( + `Fleet roster at ${rosterPath} is invalid. Fix the file or run \`mosaic fleet init --force\``, + ); + } finally { + await rm(home, { recursive: true, force: true }); + } + }); + + it('reports an unreadable roster without exposing the filesystem error', async () => { + const home = await tempDir(); + try { + await expect(readFleetRosterText(home)).rejects.toThrow( + `Could not read fleet roster at ${home}. Check the file exists and is readable.`, + ); + } finally { + await rm(home, { recursive: true, force: true }); + } + }); + + it('reports a malformed roster while selecting the v1/v2 command path', () => { + expect(() => parseFleetRosterDocument('version: [', '/srv/mosaic/fleet/roster.yaml')).toThrow( + 'Fleet roster at /srv/mosaic/fleet/roster.yaml is invalid. Fix the file or run `mosaic fleet init --force`.', + ); + }); +}); + describe('fleet roster parsing', () => { let cleanup: string | undefined; diff --git a/packages/mosaic/src/commands/install-heading.spec.ts b/packages/mosaic/src/commands/install-heading.spec.ts new file mode 100644 index 0000000..1720471 --- /dev/null +++ b/packages/mosaic/src/commands/install-heading.spec.ts @@ -0,0 +1,17 @@ +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const INSTALLER_PATH = fileURLToPath(new URL('../../../../tools/install.sh', import.meta.url)); + +describe('installer CLI package heading', () => { + it('renders the scoped package name intact', async () => { + const installer = await readFile(INSTALLER_PATH, 'utf8'); + const step = installer.match(/^step\(\)\s*\{.*\}$/m)?.[0]; + + expect(step).toBeDefined(); + + expect(step).toContain('printf \'\\n%s%s%s\\n\' "$BOLD" "$*" "$RESET"'); + expect(installer).toContain('step "@mosaicstack/mosaic (npm package)"'); + }); +});