test(fleet): correct socket boundary coverage
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,14 +3,20 @@ import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { Command } from 'commander';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { registerFleetCommand, type CommandResult } from '../commands/fleet.js';
|
||||
import { registerFleetCommand, type CommandResult, type CommandRunner } from '../commands/fleet.js';
|
||||
import {
|
||||
executeFleetReconcile,
|
||||
type FleetReconcileCommandResult,
|
||||
type FleetReconcileDeps,
|
||||
type FleetReconcileResult,
|
||||
} from './fleet-reconciler.js';
|
||||
import { renderRosterV2Yaml, type FleetRosterV2, type FleetRosterV2Agent } from './roster-v2.js';
|
||||
import {
|
||||
parseRosterV2,
|
||||
renderRosterV2Yaml,
|
||||
type FleetRosterV2,
|
||||
type FleetRosterV2Agent,
|
||||
} from './roster-v2.js';
|
||||
import { FleetTmuxRuntimeTransport } from './tmux-runtime-transport.js';
|
||||
|
||||
const holderIdentity = '11111111-1111-4111-8111-111111111111';
|
||||
const stoppedAgent: FleetRosterV2Agent = {
|
||||
@@ -73,6 +79,15 @@ class FakeLifecycleHost {
|
||||
if (args.includes('list-sessions')) {
|
||||
return { stdout: `${[...this.sessions].join('\n')}\n`, stderr: '', exitCode: 0 };
|
||||
}
|
||||
if (args.includes('has-session')) {
|
||||
const targetArgument = args[args.indexOf('-t') + 1];
|
||||
const sessionName = targetArgument?.replace(/^=/, '').split(':')[0];
|
||||
return {
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
exitCode: sessionName !== undefined && this.sessions.has(sessionName) ? 0 : 1,
|
||||
};
|
||||
}
|
||||
if (args.includes('show-environment')) {
|
||||
return {
|
||||
stdout: [
|
||||
@@ -182,6 +197,32 @@ async function fixtureHome(roster: FleetRosterV2): Promise<string> {
|
||||
return home;
|
||||
}
|
||||
|
||||
async function fixtureLegacyRoster(): Promise<string> {
|
||||
const home = await mkdtemp(join(tmpdir(), 'mosaic-reconciler-acceptance-v1-'));
|
||||
cleanupDirectories.push(home);
|
||||
const fleetDirectory = join(home, 'fleet');
|
||||
await mkdir(fleetDirectory, { mode: 0o700 });
|
||||
await chmod(home, 0o700);
|
||||
await chmod(fleetDirectory, 0o700);
|
||||
const rosterPath = join(fleetDirectory, 'roster.yaml');
|
||||
await writeFile(
|
||||
rosterPath,
|
||||
[
|
||||
'version: 1',
|
||||
'transport: tmux',
|
||||
'tmux:',
|
||||
' holder_session: _holder',
|
||||
'agents:',
|
||||
' - name: coder0',
|
||||
' runtime: pi',
|
||||
' class: code',
|
||||
'',
|
||||
].join('\n'),
|
||||
{ mode: 0o600 },
|
||||
);
|
||||
return rosterPath;
|
||||
}
|
||||
|
||||
function cliProgram(home: string, host: FakeLifecycleHost): Command {
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
@@ -203,67 +244,114 @@ function captureJson(): string[] {
|
||||
}
|
||||
|
||||
describe('FCM-M3-002 reconciler lifecycle acceptance', (): void => {
|
||||
it.each([
|
||||
{ socketName: 'mosaic-fleet', socketArgs: ['-L', 'mosaic-fleet'] },
|
||||
{ socketName: '', socketArgs: [] },
|
||||
])(
|
||||
'observes drift on the exact "$socketName" socket without runtime mutation',
|
||||
async ({ socketName, socketArgs }): Promise<void> => {
|
||||
const roster: FleetRosterV2 = {
|
||||
...baseRoster,
|
||||
tmux: { ...baseRoster.tmux, socketName },
|
||||
it('observes named-socket drift through canonical roster-v2 parsing without runtime mutation', async (): Promise<void> => {
|
||||
const roster: FleetRosterV2 = {
|
||||
...baseRoster,
|
||||
agents: [
|
||||
stoppedAgent,
|
||||
{
|
||||
...stoppedAgent,
|
||||
name: 'reviewer0',
|
||||
alias: 'Reviewer 0',
|
||||
lifecycle: { enabled: true, desiredState: 'running' },
|
||||
},
|
||||
{
|
||||
...stoppedAgent,
|
||||
name: 'validator0',
|
||||
alias: 'Validator 0',
|
||||
lifecycle: { enabled: false, desiredState: 'stopped' },
|
||||
},
|
||||
],
|
||||
};
|
||||
const home = await fixtureHome(roster);
|
||||
const host = new FakeLifecycleHost(roster);
|
||||
host.sessions.add('coder0');
|
||||
host.sessions.add('validator0');
|
||||
host.sessions.add('coder0-shadow');
|
||||
const output = captureJson();
|
||||
|
||||
await cliProgram(home, host).parseAsync(['node', 'mosaic', 'fleet', 'status']);
|
||||
|
||||
expect(output).toHaveLength(1);
|
||||
expect(JSON.parse(output[0] ?? '{}')).toMatchObject({
|
||||
plan: {
|
||||
agents: [
|
||||
stoppedAgent,
|
||||
{
|
||||
...stoppedAgent,
|
||||
name: 'reviewer0',
|
||||
alias: 'Reviewer 0',
|
||||
lifecycle: { enabled: true, desiredState: 'running' },
|
||||
},
|
||||
{
|
||||
...stoppedAgent,
|
||||
name: 'validator0',
|
||||
alias: 'Validator 0',
|
||||
lifecycle: { enabled: false, desiredState: 'stopped' },
|
||||
},
|
||||
{ name: 'coder0', drift: ['unexpected-session'] },
|
||||
{ name: 'reviewer0', drift: ['missing-session'] },
|
||||
{ name: 'validator0', drift: ['unexpected-session', 'disabled-running'] },
|
||||
],
|
||||
};
|
||||
const host = new FakeLifecycleHost(roster);
|
||||
host.sessions.add('coder0');
|
||||
host.sessions.add('validator0');
|
||||
host.sessions.add('coder0-shadow');
|
||||
unmanagedSessions: ['coder0-shadow'],
|
||||
},
|
||||
});
|
||||
expect(host.calls[0]).toEqual([
|
||||
'tmux',
|
||||
'-L',
|
||||
'mosaic-fleet',
|
||||
'list-sessions',
|
||||
'-F',
|
||||
'#{session_name}',
|
||||
]);
|
||||
expect(
|
||||
host.calls.every(
|
||||
(call: string[]): boolean =>
|
||||
call[0] !== 'tmux' || (call[1] === '-L' && call[2] === 'mosaic-fleet'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
host.calls.every((call: string[]): boolean => call[0] !== 'systemctl' || call[2] === 'show'),
|
||||
).toBe(true);
|
||||
expect(process.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
const result = await execute(host, 'status');
|
||||
|
||||
expect(result.plan.agents).toMatchObject([
|
||||
{ name: 'coder0', drift: ['unexpected-session'] },
|
||||
{ name: 'reviewer0', drift: ['missing-session'] },
|
||||
{ name: 'validator0', drift: ['unexpected-session', 'disabled-running'] },
|
||||
]);
|
||||
expect(result.plan.unmanagedSessions).toEqual(['coder0-shadow']);
|
||||
expect(host.calls[0]).toEqual([
|
||||
'tmux',
|
||||
...socketArgs,
|
||||
'list-sessions',
|
||||
'-F',
|
||||
'#{session_name}',
|
||||
]);
|
||||
expect(
|
||||
host.calls.every((call: string[]): boolean => {
|
||||
if (call[0] !== 'tmux') return true;
|
||||
return socketArgs.length === 0
|
||||
? !call.includes('-L')
|
||||
: call[1] === '-L' && call[2] === socketName;
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
host.calls.every(
|
||||
(call: string[]): boolean => call[0] !== 'systemctl' || call[2] === 'show',
|
||||
),
|
||||
).toBe(true);
|
||||
it.each([
|
||||
{
|
||||
label: 'missing',
|
||||
source: renderRosterV2Yaml(baseRoster).replace(/^ socket_name:.*\n/m, ''),
|
||||
},
|
||||
{
|
||||
label: 'empty',
|
||||
source: renderRosterV2Yaml(baseRoster).replace(/^ socket_name:.*$/m, ' socket_name: ""'),
|
||||
},
|
||||
])(
|
||||
'rejects a $label canonical roster-v2 tmux socket through parseRosterV2',
|
||||
({ source }): void => {
|
||||
expect(() => parseRosterV2(source, 'yaml')).toThrow(
|
||||
'Roster v2 tmux socket_name is required and must be a non-empty string.',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('uses the literal default tmux server only through the legacy-v1 loader and runtime transport boundary', async (): Promise<void> => {
|
||||
const rosterPath = await fixtureLegacyRoster();
|
||||
const runner = vi.fn<CommandRunner>(
|
||||
async (): Promise<CommandResult> => ({
|
||||
stdout: '111 pi 0 0 0 0\n',
|
||||
stderr: '',
|
||||
exitCode: 0,
|
||||
}),
|
||||
);
|
||||
const transport = new FleetTmuxRuntimeTransport({
|
||||
mosaicHome: '/unused',
|
||||
rosterPath,
|
||||
runner,
|
||||
});
|
||||
|
||||
await expect(transport.verifySession('coder0')).resolves.toEqual({
|
||||
id: 'coder0',
|
||||
runtimeId: 'pi',
|
||||
socketName: '',
|
||||
});
|
||||
expect(runner).toHaveBeenCalledTimes(1);
|
||||
expect(runner).toHaveBeenCalledWith('tmux', [
|
||||
'list-panes',
|
||||
'-t',
|
||||
'=coder0:0.0',
|
||||
'-F',
|
||||
'#{pane_pid} #{pane_current_command} #{pane_dead} #{pane_activity} #{window_activity} #{session_activity}',
|
||||
]);
|
||||
expect(runner.mock.calls[0]?.[1]).not.toContain('-L');
|
||||
});
|
||||
|
||||
it('classifies unmanaged near-collisions and stops only the exact roster-owned service', async (): Promise<void> => {
|
||||
const host = new FakeLifecycleHost(baseRoster);
|
||||
host.sessions.add('coder0');
|
||||
|
||||
Reference in New Issue
Block a user