fix(test): guard the CASES_DIR delete + harden the data-dir teardown

PR #356 stopped the remote-hosts.json fixture write from clobbering prod.
Two holes in the same file remain:

1. The quick-start afterEach still ran rmSync(CASES_DIR, recursive).
   CASES_DIR is join(homedir(), 'codeman-cases'), and on Linux builds
   where os.homedir() reads /etc/passwd instead of $HOME it resolves to
   the PROD case tree - so a full-suite run deleted the real
   ~/codeman-cases. Add a shared safeRmHomeTree() containment gate that
   only deletes a path under the redirected test HOME.

2. setup.ts teardown did rmSync(process.env.CODEMAN_DATA_DIR ?? '') AFTER
   restoring the env - if a pre-existing prod CODEMAN_DATA_DIR was set,
   that deleted prod. Capture the throwaway dir in a const and clean that.

A broader test-isolation sweep (10 files: cli-skill-target, edge-cases,
integration-flows, operation-lightspeed, ralph-integration,
case-clone-routes, voice-routes, session-cleanup, sse-events,
sse-subscription-filter) also applies the same containment gates to every
per-case delete. It is intentionally NOT included here to keep this PR
skinny; it is identified and available on request.
This commit is contained in:
timkjr
2026-09-02 20:41:24 -05:00
parent 2694d3f74a
commit ff88b6957e
4 changed files with 53 additions and 6 deletions
+7 -3
View File
@@ -20,6 +20,7 @@ const originalVitest = process.env.VITEST;
const originalPlaywrightBrowsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH;
const originalCodemanDataDir = process.env.CODEMAN_DATA_DIR;
const testHome = mkdtempSync(join(tmpdir(), 'codeman-vitest-'));
const testDataDir = join(tmpdir(), `codeman-vitest-data-${process.pid}`);
if (originalPlaywrightBrowsersPath === undefined && originalHome) {
process.env.PLAYWRIGHT_BROWSERS_PATH =
@@ -41,7 +42,7 @@ process.env.VITEST = 'true';
// overwrote prod `remote-hosts.json` with an `h1/box/10.0.0.5` fixture during a
// bare full-suite run, wiping every user-defined remote host and emptying the
// launch case dropdown). Point every test at a throwaway data dir instead.
process.env.CODEMAN_DATA_DIR = join(tmpdir(), `codeman-vitest-data-${process.pid}`);
process.env.CODEMAN_DATA_DIR = testDataDir;
delete process.env.CODEMAN_PASSWORD;
delete process.env.CODEMAN_USERNAME;
@@ -61,7 +62,9 @@ afterAll(async () => {
// "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).
await new Promise((resolve) => setTimeout(resolve, 50));
const { promise: drained, resolve: drainDone } = Promise.withResolvers<void>();
setTimeout(drainDone, 50);
await drained;
if (originalHome === undefined) delete process.env.HOME;
else process.env.HOME = originalHome;
@@ -79,7 +82,7 @@ afterAll(async () => {
else process.env.CODEMAN_DATA_DIR = originalCodemanDataDir;
rmSync(testHome, { recursive: true, force: true });
rmSync(process.env.CODEMAN_DATA_DIR ?? '', { recursive: true, force: true });
rmSync(testDataDir, { recursive: true, force: true });
});
// afterAll never fires for a fully-skipped test file (no tests execute), which
@@ -87,4 +90,5 @@ afterAll(async () => {
// with force is a no-op when afterAll already removed it.
process.on('exit', () => {
rmSync(testHome, { recursive: true, force: true });
rmSync(testDataDir, { recursive: true, force: true });
});