import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { buildReseedCommand, buildRelaunchCommands, readRosterAgentNames, runFrameworkReseed, } from './update-checker.js'; /** * F3-m3 / R13: `mosaic update` re-seeds the framework + (opt-in) relaunches * durable agents so shipped launcher/runtime changes activate. These cover the * pure builders + the missing-installer guard (the exec path is integration). */ describe('buildReseedCommand', () => { it('invokes the package install.sh in data-safe sync-only keep mode', () => { const out = buildReseedCommand('/pkg/framework', '/home/u/.config/mosaic'); expect(out.installer).toBe('/pkg/framework/install.sh'); expect(out.command).toBe('bash /pkg/framework/install.sh'); expect(out.env).toEqual({ MOSAIC_SYNC_ONLY: '1', MOSAIC_INSTALL_MODE: 'keep', MOSAIC_HOME: '/home/u/.config/mosaic', }); }); }); describe('buildRelaunchCommands', () => { it('builds a systemctl --user restart per agent unit', () => { expect(buildRelaunchCommands(['orchestrator', 'coder0'])).toEqual([ ['systemctl', '--user', 'restart', 'mosaic-agent@orchestrator.service'], ['systemctl', '--user', 'restart', 'mosaic-agent@coder0.service'], ]); }); it('is empty for an empty roster', () => { expect(buildRelaunchCommands([])).toEqual([]); }); }); describe('readRosterAgentNames', () => { let home: string; beforeEach(() => { home = mkdtempSync(join(tmpdir(), 'mosaic-roster-')); }); afterEach(() => { rmSync(home, { recursive: true, force: true }); }); it('returns [] when no roster exists', () => { expect(readRosterAgentNames(home)).toEqual([]); }); it('extracts agent names from roster.yaml', () => { mkdirSync(join(home, 'fleet'), { recursive: true }); writeFileSync( join(home, 'fleet', 'roster.yaml'), [ 'version: 1', 'agents:', ' - name: orchestrator', ' runtime: pi', ' - name: coder0', ' runtime: claude', ' - name: "reviewer-1"', ' runtime: codex', ].join('\n') + '\n', ); expect(readRosterAgentNames(home)).toEqual(['orchestrator', 'coder0', 'reviewer-1']); }); }); describe('runFrameworkReseed', () => { it('reports not-ok (not throw) when the installer is absent', () => { const missing = mkdtempSync(join(tmpdir(), 'mosaic-noinstaller-')); const res = runFrameworkReseed(missing, join(missing, 'home')); expect(res.ok).toBe(false); expect(res.reason).toContain('installer not found'); rmSync(missing, { recursive: true, force: true }); }); });