From b093b0f74561888fb48b7cb5b6aa7fc9884619a1 Mon Sep 17 00:00:00 2001 From: orendi84 Date: Sat, 18 Apr 2026 20:25:08 +0200 Subject: [PATCH] test(e2e): isolate HOME in Setup Journey + Init Edge Cases blocks The Setup Journey block runs `gbrain init --non-interactive --url $DATABASE_URL` in a subprocess that inherits the real HOME. On init, gbrain calls saveConfig() which writes the test URL into the developer's real ~/.gbrain/config.json, clobbering their prod Supabase URL + any API keys stored there. On a developer machine running `DATABASE_URL=postgresql://postgres:postgres@localhost:5434/gbrain_test bun run test:e2e` (the exact command in CONTRIBUTING.md), the test replaces the live config with: {"engine":"postgres","database_url":"postgresql://postgres:postgres@localhost:5434/gbrain_test"} Same latent bug in Init Edge Cases (though the one existing test there strips DATABASE_URL so it doesn't actually hit saveConfig, applied the fix defensively). Fix: override process.env.HOME to a fresh mkdtempSync() dir in beforeAll, restore in afterAll, and pass HOME explicitly to spawned subprocesses. Matches the pattern used in test/e2e/migration-flow.test.ts (the only other e2e test that had the discipline to do this correctly). No production code changes. No new dependencies. +36/-4 lines. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/e2e/mechanical.test.ts | 40 +++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/test/e2e/mechanical.test.ts b/test/e2e/mechanical.test.ts index 0b6175bb..f0290a98 100644 --- a/test/e2e/mechanical.test.ts +++ b/test/e2e/mechanical.test.ts @@ -580,13 +580,31 @@ describeE2E('E2E: Idempotency', () => { // ───────────────────────────────────────────────────────────────── describeE2E('E2E: Setup Journey', () => { + // Isolate HOME so `gbrain init --non-interactive --url ...` cannot clobber + // the developer's real ~/.gbrain/config.json when tests run against + // Supabase creds in CI, or against localhost in dev. See the pattern in + // test/e2e/migration-flow.test.ts for the canonical HOME-override setup. + let origHome: string | undefined; + let tempHome: string | undefined; + beforeAll(async () => { + origHome = process.env.HOME; + tempHome = mkdtempSync(join(tmpdir(), 'gbrain-e2e-setup-home-')); + process.env.HOME = tempHome; await setupDB(); }); - afterAll(teardownDB); + afterAll(async () => { + await teardownDB(); + if (origHome === undefined) delete process.env.HOME; + else process.env.HOME = origHome; + try { if (tempHome) rmSync(tempHome, { recursive: true, force: true }); } catch { /* best-effort */ } + }); const cliCwd = join(import.meta.dir, '../..'); - const cliEnv = () => ({ ...process.env, DATABASE_URL: process.env.DATABASE_URL! }); + // Pass HOME explicitly so subprocesses see the temp HOME, not the caller's + // real HOME. Without this, ...process.env would have already been captured + // at module load before the beforeAll override took effect. + const cliEnv = () => ({ ...process.env, DATABASE_URL: process.env.DATABASE_URL!, HOME: process.env.HOME! }); test('gbrain init --non-interactive connects and initializes', () => { const result = Bun.spawnSync({ @@ -650,10 +668,24 @@ describeE2E('E2E: Setup Journey', () => { // ───────────────────────────────────────────────────────────────── describeE2E('E2E: Init Edge Cases', () => { - afterAll(teardownDB); + // Same HOME-isolation rationale as the Setup Journey block above. + let origHome: string | undefined; + let tempHome: string | undefined; + + beforeAll(() => { + origHome = process.env.HOME; + tempHome = mkdtempSync(join(tmpdir(), 'gbrain-e2e-init-edge-home-')); + process.env.HOME = tempHome; + }); + afterAll(async () => { + await teardownDB(); + if (origHome === undefined) delete process.env.HOME; + else process.env.HOME = origHome; + try { if (tempHome) rmSync(tempHome, { recursive: true, force: true }); } catch { /* best-effort */ } + }); test('init --non-interactive without URL fails gracefully', () => { - const env = { ...process.env }; + const env = { ...process.env, HOME: process.env.HOME! }; delete env.DATABASE_URL; delete env.GBRAIN_DATABASE_URL; const result = Bun.spawnSync({