clawdie-ai/setup/install-mode.test.ts

200 lines
6 KiB
TypeScript
Raw Normal View History

import fs from 'fs';
import path from 'path';
import { afterEach, describe, expect, it } from 'vitest';
import { detectExistingInstall, resolveInstallMode } from './install-mode.js';
refactor(identity): remove PLATFORM_ID/SERVICE_NAME/RUNTIME_USER env vars Step 5 of system-namespace cutover: complete the env-var removal that step 4 set up. All consumers now import SERVICE_NAME from src/platform-identity.ts directly; the deprecated PLATFORM_* re-exports in src/config.ts are gone. src/config.ts: - PLATFORM_ID, PLATFORM_SERVICE_NAME, PLATFORM_RUNTIME_USER exports removed. - PLATFORM_RUNTIME_HOME stays (derived from SERVICE_NAME, used by ~10 consumers for path construction). - Env-var allowlist drops PLATFORM_ID / PLATFORM_SERVICE_NAME / PLATFORM_RUNTIME_USER / PLATFORM_RUNTIME_HOME entries. - CONTROLPLANE_AIDER_TMUX_SESSION uses SERVICE_NAME directly. setup/onboarding.ts: - writeIdentity() simplified to write only ASSISTANT_NAME (display). PLATFORM_ID / PLATFORM_SERVICE_NAME / PLATFORM_RUNTIME_USER are no longer written to .env. Fresh installs have no PLATFORM_* keys. - Status emission switched from PLATFORM_ID to SERVICE_NAME. setup/env-audit.ts: - Audit lists SERVICE_NAME instead of PLATFORM_ID; the env-file PLATFORM_ID read is gone. 24 source files (src/*.ts, setup/*.ts, scripts/dashboard.ts): - Bare PLATFORM_ID / PLATFORM_SERVICE_NAME / PLATFORM_RUNTIME_USER references replaced with SERVICE_NAME. - Imports rewired: SERVICE_NAME comes from ../{src/}platform-identity.js, not from config.js. - Imports deduped where the sed sweep produced collisions. Shell scripts (scripts/bhyve-evidence.sh, glass.sh, inspect-system.sh): - Hardcoded SERVICE_NAME='clawdie' and SERVICE_USER='clawdie'. No more grep-the-.env fallbacks; the constants are the source. Tests (middle path): - Mechanical fixes (import path, renamed assertion text): src/hostd/privileged-commands.test.ts, src/startup-report.test.ts, setup/env-audit.test.ts, setup/install-mode.test.ts. - Skipped with `// system-namespace:` markers (pinned removed env-driven override behavior; Codex rewrites once the bootstrap- config service-user override path lands): setup/verify.test.ts > 'uses the platform service name for PID candidates' setup/service.test.ts > 'resolves a platform runtime separately from the tenant' Test files still containing PLATFORM_* strings in vi.mock contents, ENV_KEYS arrays, or comments are left untouched — they are test artifacts that don't affect runtime; mock contents resolve to 'clawdie' which still equals SERVICE_NAME. tsc clean. 2095 tests pass, 4 skipped, 0 fail. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --- Build: pass | Tests: pass — Tests 2095 passed | 4 skipped (2099)
2026-05-02 14:49:19 +02:00
import { SERVICE_NAME } from '../src/platform-identity.js';
function makeProjectRoot(): string {
const base = path.join(process.cwd(), 'tmp', 'tests');
fs.mkdirSync(base, { recursive: true });
return fs.mkdtempSync(path.join(base, 'clawdie-install-mode-'));
}
function removeDir(dirPath: string): void {
fs.rmSync(dirPath, { recursive: true, force: true });
}
describe('detectExistingInstall', () => {
const roots: string[] = [];
afterEach(() => {
while (roots.length > 0) {
removeDir(roots.pop()!);
}
});
it('treats two soft signals as an existing install', () => {
const root = makeProjectRoot();
roots.push(root);
fs.writeFileSync(path.join(root, '.env'), 'OPENROUTER_API_KEY=test\n');
fs.mkdirSync(path.join(root, 'groups'), { recursive: true });
fs.writeFileSync(path.join(root, 'groups', 'ops.json'), '{}');
const detection = detectExistingInstall(root, {
existsSync: (target) =>
fs.existsSync(target) &&
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
commandExists: () => false,
execFileSync: (() => {
throw new Error('not called');
}) as typeof import('child_process').execFileSync,
});
expect(detection.existing).toBe(true);
expect(detection.softSignals).toEqual(['env', 'groups']);
expect(detection.strongSignals).toEqual([]);
expect(detection.installUuid).toBeNull();
});
it('treats a strong signal as an existing install', () => {
const root = makeProjectRoot();
roots.push(root);
const detection = detectExistingInstall(root, {
existsSync: (target) =>
target === `/usr/local/etc/rc.d/${SERVICE_NAME}` ||
fs.existsSync(target),
commandExists: () => false,
execFileSync: (() => {
throw new Error('not called');
}) as typeof import('child_process').execFileSync,
});
expect(detection.existing).toBe(true);
expect(detection.strongSignals).toContain('service');
expect(detection.installUuid).toBeNull();
});
it('prefers system.env zfs pool/prefix when matching datasets', () => {
const root = makeProjectRoot();
roots.push(root);
fs.writeFileSync(
path.join(root, 'system.env'),
['ZFS_POOL=tank', 'ZFS_PREFIX=alpha-runtime', ''].join('\n'),
);
const detection = detectExistingInstall(root, {
existsSync: (target) =>
fs.existsSync(target) &&
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
commandExists: (name) => name === 'zfs',
execFileSync: ((cmd: string) => {
if (cmd === 'zfs')
return 'tank/alpha-runtime\ntank/alpha-runtime/pgdata\n';
throw new Error('unexpected command');
}) as typeof import('child_process').execFileSync,
});
expect(detection.existing).toBe(true);
expect(detection.strongSignals).toContain('zfs');
expect(detection.rootDataset).toBe('tank/alpha-runtime');
});
it('treats persisted zfs install metadata as a strong signal', () => {
const root = makeProjectRoot();
roots.push(root);
fs.writeFileSync(
path.join(root, 'system.env'),
['ZFS_POOL=tank', 'ZFS_PREFIX=alpha-runtime', ''].join('\n'),
);
const detection = detectExistingInstall(root, {
existsSync: (target) =>
fs.existsSync(target) &&
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
commandExists: (name) => name === 'zfs',
execFileSync: ((cmd: string, args: string[]) => {
if (cmd !== 'zfs') throw new Error('unexpected command');
if (args[0] === 'list') return '';
if (args[0] === 'get') return 'abc123\n';
throw new Error('unexpected zfs args');
}) as typeof import('child_process').execFileSync,
});
expect(detection.existing).toBe(true);
expect(detection.strongSignals).toContain('zfs-metadata');
expect(detection.installUuid).toBe('abc123');
});
it('returns fresh when no signals are present', () => {
const root = makeProjectRoot();
roots.push(root);
const detection = detectExistingInstall(root, {
existsSync: (target) =>
fs.existsSync(target) &&
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
commandExists: () => false,
execFileSync: (() => {
throw new Error('not called');
}) as typeof import('child_process').execFileSync,
});
expect(detection.existing).toBe(false);
});
});
describe('resolveInstallMode', () => {
const freshDetection = {
signals: {
envFile: false,
groupsDir: false,
serviceFile: false,
zfsDataset: false,
zfsMetadata: false,
runtimeUser: false,
},
strongSignals: [],
softSignals: [],
existing: false,
rootDataset: null,
installUuid: null,
};
const existingDetection = {
signals: {
envFile: true,
groupsDir: true,
serviceFile: false,
zfsDataset: true,
zfsMetadata: false,
runtimeUser: false,
},
strongSignals: ['zfs'],
softSignals: ['env', 'groups'],
existing: true,
rootDataset: 'zroot/clawdie-runtime',
installUuid: null,
};
it('resolves auto to fresh when nothing exists', () => {
expect(resolveInstallMode('auto', freshDetection).effective).toBe('fresh');
});
it('resolves auto to upgrade when an install exists', () => {
expect(resolveInstallMode('auto', existingDetection).effective).toBe(
'upgrade',
);
});
it('rejects fresh against an existing install', () => {
expect(() => resolveInstallMode('fresh', existingDetection)).toThrow(
/INSTALL_MODE=fresh/,
);
});
it('rejects upgrade when no install exists', () => {
expect(() => resolveInstallMode('upgrade', freshDetection)).toThrow(
/INSTALL_MODE=upgrade/,
);
});
it('rejects rescue when no install exists', () => {
expect(() => resolveInstallMode('rescue', freshDetection)).toThrow(
/INSTALL_MODE=rescue/,
);
});
});