import { describe, expect, it, vi } from 'vitest' import type { AgentConfig } from '../src/config/agentConfig.js' import { RootRefusedError, detectPlatform, installService, uninstallService, type InstallDeps, } from '../src/service/install.js' const CFG: AgentConfig = { relayUrl: 'wss://relay/agent', enrollUrl: 'https://x/enroll', stateDir: '/tmp/x', localTargetUrl: 'ws://127.0.0.1:3000', subdomain: 'host-42', hostId: 'h-1', } function deps(uid = 501): InstallDeps & { writes: Array<[string, string]>; runs: Array<[string, readonly string[]]> } { const writes: Array<[string, string]> = [] const runs: Array<[string, readonly string[]]> = [] return { writes, runs, writeFile: (p, c) => writes.push([p, c]), runCommand: async (cmd, args) => { runs.push([cmd, args]) }, getuid: () => uid, homedir: () => '/home/alice', username: () => 'alice', binPath: () => '/usr/local/bin/web-terminal-agent', } } describe('detectPlatform (T17)', () => { it('maps darwin→launchd, linux→systemd, else null', () => { expect(detectPlatform('darwin')).toBe('launchd') expect(detectPlatform('linux')).toBe('systemd') expect(detectPlatform('win32')).toBeNull() }) }) describe('installService (T17)', () => { it('refuses to install as root (negative, least privilege)', async () => { await expect(installService(CFG, 'systemd', deps(0))).rejects.toBeInstanceOf(RootRefusedError) }) it('systemd: writes a run-as-user unit and enables it', async () => { const d = deps() await installService(CFG, 'systemd', d) const [, unit] = d.writes[0]! expect(unit).toContain('ExecStart=/usr/local/bin/web-terminal-agent run') expect(unit).toContain('User=alice') expect(unit).not.toContain('User=root') expect(unit).toContain('Restart=on-failure') expect(d.runs[0]![0]).toBe('systemctl') }) it('launchd: writes a plist with ProgramArguments run + KeepAlive', async () => { const d = deps() await installService(CFG, 'launchd', d) const [path, plist] = d.writes[0]! expect(path).toContain('LaunchAgents') expect(plist).toContain('run') expect(plist).toContain('KeepAlive') expect(d.runs[0]![0]).toBe('launchctl') }) it('uninstall unloads cleanly', async () => { const d = deps() await uninstallService('launchd', d) expect(d.runs[0]).toEqual(['launchctl', ['unload', '/home/alice/Library/LaunchAgents/com.web-terminal.agent.plist']]) }) })