From 38de24787d2b1fdca880e782ac37dbea3dd36346 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Wed, 15 Jul 2026 09:37:32 -0500 Subject: [PATCH] test(fleet): stabilize reconciler CI fixtures --- packages/mosaic/src/commands/fleet.spec.ts | 30 ++++++++++++++----- .../mosaic/src/fleet/fleet-reconciler.spec.ts | 9 +++--- packages/mosaic/src/fleet/fleet-reconciler.ts | 3 +- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/mosaic/src/commands/fleet.spec.ts b/packages/mosaic/src/commands/fleet.spec.ts index edcae29..3ed159e 100644 --- a/packages/mosaic/src/commands/fleet.spec.ts +++ b/packages/mosaic/src/commands/fleet.spec.ts @@ -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 runner: CommandRunner = async (command, args) => { calls.push([command, ...args]); @@ -501,11 +507,14 @@ describe('fleet command construction', () => { }; const program = new Command(); program.exitOverride(); - registerFleetCommand(program, { runner }); + registerFleetCommand(program, { runner, mosaicHome: home }); - await program.parseAsync(['node', 'mosaic', 'fleet', 'status']); - - expect(calls).toEqual([['systemctl', '--user', 'status', 'mosaic-tmux-holder.service']]); + try { + await program.parseAsync(['node', 'mosaic', 'fleet', 'status']); + 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 () => { @@ -621,13 +630,19 @@ describe('fleet command construction', () => { ).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 stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); const runner: CommandRunner = async () => ({ stdout: '', stderr: 'missing\n', exitCode: 3 }); const program = new Command(); program.exitOverride(); - registerFleetCommand(program, { runner }); + registerFleetCommand(program, { runner, mosaicHome: home }); try { await program.parseAsync(['node', 'mosaic', 'fleet', 'status']); @@ -635,6 +650,7 @@ describe('fleet command construction', () => { } finally { process.exitCode = originalExitCode; stderrSpy.mockRestore(); + await rm(home, { recursive: true, force: true }); } }); diff --git a/packages/mosaic/src/fleet/fleet-reconciler.spec.ts b/packages/mosaic/src/fleet/fleet-reconciler.spec.ts index 7700992..a4f764e 100644 --- a/packages/mosaic/src/fleet/fleet-reconciler.spec.ts +++ b/packages/mosaic/src/fleet/fleet-reconciler.spec.ts @@ -134,11 +134,10 @@ describe('fleet roster-owned reconciler', (): void => { }); await rm(lockPath); - await chmod(fleet, 0o500); - await expect(acquirePrivateReconcileLock(home)()).rejects.toMatchObject({ - code: 'lock-io-failed', - }); - await chmod(fleet, 0o700); + const ioFailure = Object.assign(new Error('injected I/O failure'), { code: 'EIO' }); + await expect( + acquirePrivateReconcileLock(home, async () => Promise.reject(ioFailure))(), + ).rejects.toMatchObject({ code: 'lock-io-failed' }); }); it('does not unlink a replacement lock and normally releases its own lock', async (): Promise => { diff --git a/packages/mosaic/src/fleet/fleet-reconciler.ts b/packages/mosaic/src/fleet/fleet-reconciler.ts index 4593a5f..1dc2f17 100644 --- a/packages/mosaic/src/fleet/fleet-reconciler.ts +++ b/packages/mosaic/src/fleet/fleet-reconciler.ts @@ -609,6 +609,7 @@ function mosaicHomeFor(deps: FleetReconcileDeps): string { /** Acquires a private lock only after proving the canonical managed path. */ export function acquirePrivateReconcileLock( mosaicHome: string, + openLock: typeof open = open, ): () => Promise<() => Promise> { const fleetDir = join(mosaicHome, 'fleet'); const lockPath = join(fleetDir, 'roster.yaml.reconcile.lock'); @@ -619,7 +620,7 @@ export function acquirePrivateReconcileLock( let handle: FileHandle; try { - handle = await open(lockPath, 'wx', 0o600); + handle = await openLock(lockPath, 'wx', 0o600); } catch (error: unknown) { if (isCode(error, 'EEXIST')) { await assertSafeLockLeafIfPresent(lockPath);