Files
Codeman/test/test-home-guard.test.ts
T
Devvyn 710ed73585 fix(test): a real home is never "the test HOME" to the deletion guards
safeRmHomeTree and isUnderTestHome answered "is this path under process.env.HOME?".
With test/setup.ts skipped (a bare vitest) HOME is the developer's real home, so every
real file passed, which is how the real linked-cases registry was deleted on 2026-10-10.

They now also require HOME to be the throwaway codeman-vitest-* directory setup.ts
creates, and the shared route harness throws when it is not, so every route test refuses
to start against a real ~/.codeman rather than just the one file that checked.
2026-10-17 05:07:38 +08:00

79 lines
3.0 KiB
TypeScript

/**
* @fileoverview The test-only deletion guards must treat a REAL home as "not the test HOME".
*
* `safeRmHomeTree` and `isUnderTestHome` were written so a test could never delete production
* state, and they asked "is this path under `process.env.HOME`?". With `test/setup.ts` skipped
* (a bare `vitest`), HOME is the developer's real home, so that answer is "yes" for every real
* file, and on 2026-10-10 they approved wiping the real linked-cases registry. They now also
* require HOME to be the throwaway directory setup.ts made.
*
* Port: none. Everything happens in a temp tree; HOME is stubbed for the duration of a test.
*/
import { afterEach, describe, expect, it, vi } from 'vitest';
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
TEST_HOME_PREFIX,
assertSandboxedHome,
isSandboxedHome,
isUnderTestHome,
safeRmHomeTree,
} from './mocks/index.js';
import { createRouteTestHarness } from './routes/_route-test-utils.js';
const made: string[] = [];
/** A stand-in for a developer's REAL home: a real directory with real files, not a sandbox name. */
function fakeRealHome(): string {
const dir = mkdtempSync(join(tmpdir(), 'not-the-sandbox-'));
made.push(dir);
mkdirSync(join(dir, '.codeman'));
writeFileSync(join(dir, '.codeman', 'linked-cases.json'), '{"keep":"/me"}');
return dir;
}
afterEach(() => {
vi.unstubAllEnvs();
for (const d of made.splice(0)) rmSync(d, { recursive: true, force: true });
});
describe('test HOME guards', () => {
it('this suite really runs under the sandboxed HOME', () => {
expect(isSandboxedHome()).toBe(true);
expect(() => assertSandboxedHome()).not.toThrow();
});
it('pins the prefix test/setup.ts creates', () => {
expect(TEST_HOME_PREFIX).toBe('codeman-vitest-');
});
it('safeRmHomeTree does not delete anything under a real home', () => {
const home = fakeRealHome();
vi.stubEnv('HOME', home);
safeRmHomeTree(join(home, '.codeman'));
expect(existsSync(join(home, '.codeman', 'linked-cases.json'))).toBe(true);
});
it('isUnderTestHome is false for a real home, even for a file inside it', () => {
const home = fakeRealHome();
vi.stubEnv('HOME', home);
expect(isSandboxedHome()).toBe(false);
expect(isUnderTestHome(join(home, '.codeman', 'linked-cases.json'))).toBe(false);
});
it('still deletes inside the real sandbox', () => {
const inside = mkdtempSync(join(process.env.HOME as string, 'guard-'));
writeFileSync(join(inside, 'f'), 'x');
expect(isUnderTestHome(inside)).toBe(true);
safeRmHomeTree(inside);
expect(existsSync(inside)).toBe(false);
});
it('the route harness refuses to start against a real home', async () => {
const home = fakeRealHome();
vi.stubEnv('HOME', home);
await expect(createRouteTestHarness(() => undefined)).rejects.toThrow(/temp HOME/);
expect(existsSync(join(home, '.codeman', 'linked-cases.json'))).toBe(true);
});
});