2026-01-22 11:30:10 +01:00
|
|
|
/**
|
2026-02-26 16:44:34 +01:00
|
|
|
* @fileoverview Global test setup for Codeman tests
|
2026-01-22 11:30:10 +01:00
|
|
|
*
|
2026-07-29 09:11:53 +03:00
|
|
|
* SAFETY: The suite gets a temporary HOME and explicitly enables runtime test
|
|
|
|
|
* mode before application modules load. Tests therefore cannot touch the real
|
|
|
|
|
* Codeman state/cases tree or launch external tmux-backed agent sessions.
|
2026-01-22 19:24:36 +01:00
|
|
|
*
|
2026-06-08 10:19:38 -04:00
|
|
|
* This setup file strips shell-level auth configuration that can leak from a
|
|
|
|
|
* running Codeman instance, then handles mock/timer cleanup between tests.
|
2026-01-22 11:30:10 +01:00
|
|
|
*/
|
|
|
|
|
|
2026-07-29 09:11:53 +03:00
|
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
|
|
|
import { tmpdir } from 'node:os';
|
|
|
|
|
import { join } from 'node:path';
|
|
|
|
|
import { afterAll, afterEach, vi } from 'vitest';
|
|
|
|
|
|
|
|
|
|
const originalHome = process.env.HOME;
|
|
|
|
|
const originalUserProfile = process.env.USERPROFILE;
|
|
|
|
|
const originalVitest = process.env.VITEST;
|
|
|
|
|
const originalPlaywrightBrowsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH;
|
2026-08-29 15:21:46 -05:00
|
|
|
const originalCodemanDataDir = process.env.CODEMAN_DATA_DIR;
|
2026-07-29 09:11:53 +03:00
|
|
|
const testHome = mkdtempSync(join(tmpdir(), 'codeman-vitest-'));
|
2026-08-30 21:43:45 -05:00
|
|
|
const testDataDir = join(tmpdir(), `codeman-vitest-data-${process.pid}`);
|
2026-07-29 09:11:53 +03:00
|
|
|
|
|
|
|
|
if (originalPlaywrightBrowsersPath === undefined && originalHome) {
|
|
|
|
|
process.env.PLAYWRIGHT_BROWSERS_PATH =
|
|
|
|
|
process.platform === 'darwin'
|
|
|
|
|
? join(originalHome, 'Library', 'Caches', 'ms-playwright')
|
|
|
|
|
: process.platform === 'win32'
|
2026-07-31 08:47:52 +02:00
|
|
|
? join(process.env.LOCALAPPDATA || join(originalHome, 'AppData', 'Local'), 'ms-playwright')
|
2026-07-29 09:11:53 +03:00
|
|
|
: join(originalHome, '.cache', 'ms-playwright');
|
|
|
|
|
}
|
|
|
|
|
process.env.HOME = testHome;
|
|
|
|
|
process.env.USERPROFILE = testHome;
|
|
|
|
|
process.env.VITEST = 'true';
|
2026-01-22 11:30:10 +01:00
|
|
|
|
2026-09-04 13:47:29 +02:00
|
|
|
// SAFETY: `getDataDir()` is `process.env.CODEMAN_DATA_DIR || join(homedir(), '.codeman<suffix>')`.
|
|
|
|
|
// The temp HOME above already redirects the second half (`os.homedir()` follows
|
|
|
|
|
// `$HOME`; libuv checks the env var before the passwd entry), but the first half
|
|
|
|
|
// is an ABSOLUTE override: a `CODEMAN_DATA_DIR` inherited from the shell (a
|
|
|
|
|
// second instance, a beta run) bypasses the temp HOME entirely, and a bare suite
|
|
|
|
|
// run then reads and writes the REAL data dir (found 2026-08-29:
|
|
|
|
|
// `session-routes-workspace-hooks.test.ts` overwrote the production
|
|
|
|
|
// `remote-hosts.json` with an `h1/box/10.0.0.5` fixture, wiping every user-defined
|
|
|
|
|
// remote host and emptying the launch case dropdown). Point it at a throwaway dir.
|
2026-08-30 21:43:45 -05:00
|
|
|
process.env.CODEMAN_DATA_DIR = testDataDir;
|
2026-08-29 15:21:46 -05:00
|
|
|
|
2026-06-08 10:19:38 -04:00
|
|
|
delete process.env.CODEMAN_PASSWORD;
|
|
|
|
|
delete process.env.CODEMAN_USERNAME;
|
2026-06-10 16:23:57 +02:00
|
|
|
// Gesture availability changes renderIndexHtml output (injects the
|
|
|
|
|
// __codemanGestureAvailable flag), breaking byte-identity assertions
|
|
|
|
|
// (test/server-index-title.test.ts) when the shell exports CODEMAN_GESTURE=1.
|
|
|
|
|
delete process.env.CODEMAN_GESTURE;
|
2026-06-08 10:19:38 -04:00
|
|
|
|
2026-01-30 22:25:59 +01:00
|
|
|
afterEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
vi.useRealTimers();
|
|
|
|
|
});
|
2026-07-29 09:11:53 +03:00
|
|
|
|
2026-07-31 08:47:52 +02:00
|
|
|
afterAll(async () => {
|
|
|
|
|
// Let in-flight console-log rpc forwards drain before the worker environment
|
|
|
|
|
// tears down. On loaded CI runners the channel otherwise closes while the last
|
|
|
|
|
// "onUserConsoleLog" call is still pending, and that single unhandled
|
|
|
|
|
// EnvironmentTeardownError fails the run after every test has passed
|
|
|
|
|
// (observed twice on the PR #175/#176 merge commit; never locally).
|
2026-08-30 21:43:45 -05:00
|
|
|
const { promise: drained, resolve: drainDone } = Promise.withResolvers<void>();
|
|
|
|
|
setTimeout(drainDone, 50);
|
|
|
|
|
await drained;
|
2026-07-31 08:47:52 +02:00
|
|
|
|
2026-07-29 09:11:53 +03:00
|
|
|
if (originalHome === undefined) delete process.env.HOME;
|
|
|
|
|
else process.env.HOME = originalHome;
|
|
|
|
|
|
|
|
|
|
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
|
|
|
|
|
else process.env.USERPROFILE = originalUserProfile;
|
|
|
|
|
|
|
|
|
|
if (originalVitest === undefined) delete process.env.VITEST;
|
|
|
|
|
else process.env.VITEST = originalVitest;
|
|
|
|
|
|
|
|
|
|
if (originalPlaywrightBrowsersPath === undefined) delete process.env.PLAYWRIGHT_BROWSERS_PATH;
|
|
|
|
|
else process.env.PLAYWRIGHT_BROWSERS_PATH = originalPlaywrightBrowsersPath;
|
|
|
|
|
|
2026-08-29 15:21:46 -05:00
|
|
|
if (originalCodemanDataDir === undefined) delete process.env.CODEMAN_DATA_DIR;
|
|
|
|
|
else process.env.CODEMAN_DATA_DIR = originalCodemanDataDir;
|
|
|
|
|
|
2026-07-29 09:11:53 +03:00
|
|
|
rmSync(testHome, { recursive: true, force: true });
|
2026-08-30 21:43:45 -05:00
|
|
|
rmSync(testDataDir, { recursive: true, force: true });
|
2026-07-29 09:11:53 +03:00
|
|
|
});
|
2026-07-31 08:47:52 +02:00
|
|
|
|
|
|
|
|
// afterAll never fires for a fully-skipped test file (no tests execute), which
|
|
|
|
|
// would leak the temp home created above. The exit hook is the backstop; rmSync
|
|
|
|
|
// with force is a no-op when afterAll already removed it.
|
|
|
|
|
process.on('exit', () => {
|
|
|
|
|
rmSync(testHome, { recursive: true, force: true });
|
2026-08-30 21:43:45 -05:00
|
|
|
rmSync(testDataDir, { recursive: true, force: true });
|
2026-07-31 08:47:52 +02:00
|
|
|
});
|