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']])
})
})