This commit was merged in pull request #818.
This commit is contained in:
@@ -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,91 @@ 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`.',
|
||||
);
|
||||
});
|
||||
|
||||
it('reports a semantically invalid v1 roster as an actionable nonzero command error', async () => {
|
||||
const home = await tempDir();
|
||||
const rosterPath = join(home, 'fleet', 'roster.yaml');
|
||||
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||||
try {
|
||||
await mkdir(dirname(rosterPath), { recursive: true });
|
||||
await writeFile(
|
||||
rosterPath,
|
||||
[
|
||||
'version: 1',
|
||||
'transport: tmux',
|
||||
'agents:',
|
||||
' - name: canary-pi',
|
||||
' runtime: pi',
|
||||
' - name: canary-pi',
|
||||
' runtime: codex',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
buildProgram().parseAsync(['node', 'mosaic', 'fleet', '--mosaic-home', home, 'ps']),
|
||||
).rejects.toMatchObject({
|
||||
code: 'fleet.roster',
|
||||
exitCode: 1,
|
||||
message: 'Fleet roster has duplicate agent name: canary-pi.',
|
||||
});
|
||||
expect(stderrSpy.mock.calls.flat().join('')).toContain(
|
||||
'Fleet roster has duplicate agent name: canary-pi.',
|
||||
);
|
||||
expect(stderrSpy.mock.calls.flat().join('')).not.toMatch(/\n\s+at\s/);
|
||||
} finally {
|
||||
stderrSpy.mockRestore();
|
||||
await rm(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('fleet roster parsing', () => {
|
||||
let cleanup: string | undefined;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user