test(fleet): stabilize reconciler CI fixtures
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
Jarvis
2026-07-15 09:37:32 -05:00
parent b48f22b50b
commit 38de24787d
3 changed files with 29 additions and 13 deletions

View File

@@ -493,7 +493,13 @@ describe('fleet command construction', () => {
]); ]);
}); });
it('runs fleet status through injected runner without touching tmux in tests', async () => { it('runs legacy fleet status through injected runner without touching tmux in tests', async () => {
const home = await tempDir();
await mkdir(join(home, 'fleet'), { recursive: true });
await writeFile(
join(home, 'fleet', 'roster.yaml'),
'version: 1\ntransport: tmux\nagents: []\n',
);
const calls: string[][] = []; const calls: string[][] = [];
const runner: CommandRunner = async (command, args) => { const runner: CommandRunner = async (command, args) => {
calls.push([command, ...args]); calls.push([command, ...args]);
@@ -501,11 +507,14 @@ describe('fleet command construction', () => {
}; };
const program = new Command(); const program = new Command();
program.exitOverride(); program.exitOverride();
registerFleetCommand(program, { runner }); registerFleetCommand(program, { runner, mosaicHome: home });
await program.parseAsync(['node', 'mosaic', 'fleet', 'status']); try {
await program.parseAsync(['node', 'mosaic', 'fleet', 'status']);
expect(calls).toEqual([['systemctl', '--user', 'status', 'mosaic-tmux-holder.service']]); expect(calls).toEqual([['systemctl', '--user', 'status', 'mosaic-tmux-holder.service']]);
} finally {
await rm(home, { recursive: true, force: true });
}
}); });
it('verifies liveness with tmux has-session and does not trust systemd active exited', async () => { it('verifies liveness with tmux has-session and does not trust systemd active exited', async () => {
@@ -621,13 +630,19 @@ describe('fleet command construction', () => {
).rejects.toThrow('Unsupported fleet profile'); ).rejects.toThrow('Unsupported fleet profile');
}); });
it('sets process exitCode when status runner fails', async () => { it('sets process exitCode when legacy status runner fails', async () => {
const home = await tempDir();
await mkdir(join(home, 'fleet'), { recursive: true });
await writeFile(
join(home, 'fleet', 'roster.yaml'),
'version: 1\ntransport: tmux\nagents: []\n',
);
const originalExitCode = process.exitCode; const originalExitCode = process.exitCode;
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
const runner: CommandRunner = async () => ({ stdout: '', stderr: 'missing\n', exitCode: 3 }); const runner: CommandRunner = async () => ({ stdout: '', stderr: 'missing\n', exitCode: 3 });
const program = new Command(); const program = new Command();
program.exitOverride(); program.exitOverride();
registerFleetCommand(program, { runner }); registerFleetCommand(program, { runner, mosaicHome: home });
try { try {
await program.parseAsync(['node', 'mosaic', 'fleet', 'status']); await program.parseAsync(['node', 'mosaic', 'fleet', 'status']);
@@ -635,6 +650,7 @@ describe('fleet command construction', () => {
} finally { } finally {
process.exitCode = originalExitCode; process.exitCode = originalExitCode;
stderrSpy.mockRestore(); stderrSpy.mockRestore();
await rm(home, { recursive: true, force: true });
} }
}); });

View File

@@ -134,11 +134,10 @@ describe('fleet roster-owned reconciler', (): void => {
}); });
await rm(lockPath); await rm(lockPath);
await chmod(fleet, 0o500); const ioFailure = Object.assign(new Error('injected I/O failure'), { code: 'EIO' });
await expect(acquirePrivateReconcileLock(home)()).rejects.toMatchObject({ await expect(
code: 'lock-io-failed', acquirePrivateReconcileLock(home, async () => Promise.reject(ioFailure))(),
}); ).rejects.toMatchObject({ code: 'lock-io-failed' });
await chmod(fleet, 0o700);
}); });
it('does not unlink a replacement lock and normally releases its own lock', async (): Promise<void> => { it('does not unlink a replacement lock and normally releases its own lock', async (): Promise<void> => {

View File

@@ -609,6 +609,7 @@ function mosaicHomeFor(deps: FleetReconcileDeps): string {
/** Acquires a private lock only after proving the canonical managed path. */ /** Acquires a private lock only after proving the canonical managed path. */
export function acquirePrivateReconcileLock( export function acquirePrivateReconcileLock(
mosaicHome: string, mosaicHome: string,
openLock: typeof open = open,
): () => Promise<() => Promise<void>> { ): () => Promise<() => Promise<void>> {
const fleetDir = join(mosaicHome, 'fleet'); const fleetDir = join(mosaicHome, 'fleet');
const lockPath = join(fleetDir, 'roster.yaml.reconcile.lock'); const lockPath = join(fleetDir, 'roster.yaml.reconcile.lock');
@@ -619,7 +620,7 @@ export function acquirePrivateReconcileLock(
let handle: FileHandle; let handle: FileHandle;
try { try {
handle = await open(lockPath, 'wx', 0o600); handle = await openLock(lockPath, 'wx', 0o600);
} catch (error: unknown) { } catch (error: unknown) {
if (isCode(error, 'EEXIST')) { if (isCode(error, 'EEXIST')) {
await assertSafeLockLeafIfPresent(lockPath); await assertSafeLockLeafIfPresent(lockPath);