diff --git a/tests/antigravity-statusline.test.ts b/tests/antigravity-statusline.test.ts index 1eca3104..8a41494b 100644 --- a/tests/antigravity-statusline.test.ts +++ b/tests/antigravity-statusline.test.ts @@ -16,9 +16,6 @@ describe('Antigravity CLI statusLine hook installer', () => { const settingsPath = join(dir, 'settings.json') const binDir = join(dir, 'bin') const codeburnPath = join(binDir, process.platform === 'win32' ? 'codeburn.cmd' : 'codeburn') - const oldSettingsPath = process.env['CODEBURN_ANTIGRAVITY_SETTINGS_PATH'] - const oldCacheDir = process.env['CODEBURN_CACHE_DIR'] - const oldPath = process.env.PATH await mkdir(binDir, { recursive: true }) await writeFile(codeburnPath, process.platform === 'win32' ? '@echo off\r\n' : '#!/bin/sh\n') await chmod(codeburnPath, 0o755) @@ -29,12 +26,6 @@ describe('Antigravity CLI statusLine hook installer', () => { try { await run(dir, settingsPath) } finally { - if (oldSettingsPath === undefined) delete process.env['CODEBURN_ANTIGRAVITY_SETTINGS_PATH'] - else process.env['CODEBURN_ANTIGRAVITY_SETTINGS_PATH'] = oldSettingsPath - if (oldCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = oldCacheDir - if (oldPath === undefined) delete process.env.PATH - else process.env.PATH = oldPath await rm(dir, { recursive: true, force: true }) } } diff --git a/tests/daily-cache.test.ts b/tests/daily-cache.test.ts index 46d774d5..98923192 100644 --- a/tests/daily-cache.test.ts +++ b/tests/daily-cache.test.ts @@ -45,7 +45,6 @@ beforeEach(() => { afterEach(async () => { vi.useRealTimers() - delete process.env['CODEBURN_CACHE_DIR'] if (existsSync(TMP_CACHE_ROOT)) { await rm(TMP_CACHE_ROOT, { recursive: true, force: true }) } diff --git a/tests/models.test.ts b/tests/models.test.ts index 40981c0a..2943f2e3 100644 --- a/tests/models.test.ts +++ b/tests/models.test.ts @@ -429,7 +429,6 @@ describe('DeepSeek v4 models resolve to pricing', () => { }) it('keeps bundled DeepSeek v4 fallback entries when runtime pricing cache is stale', async () => { - const previousCacheDir = process.env['CODEBURN_CACHE_DIR'] const cacheRoot = await mkdtemp(join(tmpdir(), 'codeburn-pricing-cache-')) try { @@ -455,11 +454,6 @@ describe('DeepSeek v4 models resolve to pricing', () => { expect(getModelCosts('deepseek-v4-pro')!.inputCostPerToken).toBe(4.35e-7) expect(getModelCosts('deepseek-v4-flash')!.inputCostPerToken).toBe(1.4e-7) } finally { - if (previousCacheDir === undefined) { - delete process.env['CODEBURN_CACHE_DIR'] - } else { - process.env['CODEBURN_CACHE_DIR'] = previousCacheDir - } await rm(cacheRoot, { recursive: true, force: true }) await loadPricing() } diff --git a/tests/optimize-fs.test.ts b/tests/optimize-fs.test.ts index 29d583e1..2364e08a 100644 --- a/tests/optimize-fs.test.ts +++ b/tests/optimize-fs.test.ts @@ -19,8 +19,6 @@ import { detectBloatedClaudeMd, detectUnusedMcp, detectBashBloat, - detectGhostAgents, - detectGhostSkills, detectGhostCommands, loadMcpConfigs, scanJsonlFile, @@ -217,16 +215,6 @@ describe('detectUnusedMcp', () => { // ============================================================================ describe('detectBashBloat', () => { - const originalEnv = process.env['BASH_MAX_OUTPUT_LENGTH'] - - beforeEach(() => { - delete process.env['BASH_MAX_OUTPUT_LENGTH'] - }) - - afterAll(() => { - if (originalEnv !== undefined) process.env['BASH_MAX_OUTPUT_LENGTH'] = originalEnv - }) - it('flags when env var is unset (uses default 30K)', () => { const finding = detectBashBloat() expect(finding).not.toBeNull() diff --git a/tests/otel-cache-aggregation.test.ts b/tests/otel-cache-aggregation.test.ts index 78f6f50b..912263d0 100644 --- a/tests/otel-cache-aggregation.test.ts +++ b/tests/otel-cache-aggregation.test.ts @@ -92,16 +92,12 @@ describe.skipIf(!isSqliteAvailable())( let tmpHome: string let tmpCache: string let dbPath: string - let prevHome: string | undefined - let prevCache: string | undefined beforeEach(async () => { tmpHome = await mkdtemp(join(tmpdir(), 'cb-otel-agg-home-')) tmpCache = await mkdtemp(join(tmpdir(), 'cb-otel-agg-cache-')) dbPath = join(tmpHome, 'agent-traces.db') - prevHome = process.env['HOME'] - prevCache = process.env['CODEBURN_CACHE_DIR'] process.env['HOME'] = tmpHome process.env['CODEBURN_CACHE_DIR'] = tmpCache @@ -116,10 +112,6 @@ describe.skipIf(!isSqliteAvailable())( afterEach(async () => { clearSessionCache() vi.unstubAllEnvs() - if (prevHome === undefined) delete process.env['HOME'] - else process.env['HOME'] = prevHome - if (prevCache === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = prevCache await rm(tmpHome, { recursive: true, force: true }) await rm(tmpCache, { recursive: true, force: true }) }) diff --git a/tests/parser-claude-cwd.test.ts b/tests/parser-claude-cwd.test.ts index 9206e6f4..57ab04b1 100644 --- a/tests/parser-claude-cwd.test.ts +++ b/tests/parser-claude-cwd.test.ts @@ -7,13 +7,9 @@ import { parseAllSessions } from '../src/parser.js' import type { DateRange } from '../src/types.js' let tmpDir: string -let originalConfigDir: string | undefined -let originalDesktopSessionsDir: string | undefined beforeEach(async () => { tmpDir = await mkdtemp(join(tmpdir(), 'claude-cwd-test-')) - originalConfigDir = process.env['CLAUDE_CONFIG_DIR'] - originalDesktopSessionsDir = process.env['CODEBURN_DESKTOP_SESSIONS_DIR'] process.env['CLAUDE_CONFIG_DIR'] = tmpDir // Point desktop sessions at an empty subdir by default so real sessions // on the developer's machine do not bleed into the unit tests. @@ -21,16 +17,6 @@ beforeEach(async () => { }) afterEach(async () => { - if (originalConfigDir === undefined) { - delete process.env['CLAUDE_CONFIG_DIR'] - } else { - process.env['CLAUDE_CONFIG_DIR'] = originalConfigDir - } - if (originalDesktopSessionsDir === undefined) { - delete process.env['CODEBURN_DESKTOP_SESSIONS_DIR'] - } else { - process.env['CODEBURN_DESKTOP_SESSIONS_DIR'] = originalDesktopSessionsDir - } await rm(tmpDir, { recursive: true, force: true }) }) diff --git a/tests/parser-gemini-cache.test.ts b/tests/parser-gemini-cache.test.ts index 240e2afc..c2542b69 100644 --- a/tests/parser-gemini-cache.test.ts +++ b/tests/parser-gemini-cache.test.ts @@ -10,24 +10,16 @@ import type { DateRange } from '../src/types.js' let home: string let cacheDir: string -let previousHome: string | undefined -let previousCacheDir: string | undefined beforeEach(async () => { home = await mkdtemp(join(tmpdir(), 'codeburn-gemini-home-')) cacheDir = await mkdtemp(join(tmpdir(), 'codeburn-gemini-cache-')) - previousHome = process.env['HOME'] - previousCacheDir = process.env['CODEBURN_CACHE_DIR'] process.env['HOME'] = home process.env['CODEBURN_CACHE_DIR'] = cacheDir }) afterEach(async () => { clearSessionCache() - if (previousHome === undefined) delete process.env['HOME'] - else process.env['HOME'] = previousHome - if (previousCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = previousCacheDir await rm(home, { recursive: true, force: true }) await rm(cacheDir, { recursive: true, force: true }) }) diff --git a/tests/parser-large-session.test.ts b/tests/parser-large-session.test.ts index 10eb7775..9ef1b8bc 100644 --- a/tests/parser-large-session.test.ts +++ b/tests/parser-large-session.test.ts @@ -16,7 +16,6 @@ beforeEach(async () => { afterEach(async () => { clearSessionCache() - delete process.env['CLAUDE_CONFIG_DIR'] await rm(home, { recursive: true, force: true }) }) diff --git a/tests/parser-local-savings.test.ts b/tests/parser-local-savings.test.ts index 166e8f07..ae5dd258 100644 --- a/tests/parser-local-savings.test.ts +++ b/tests/parser-local-savings.test.ts @@ -20,25 +20,18 @@ function makeRange(): DateRange { } let tmpDirs: string[] = [] -let originalConfigDir: string | undefined beforeAll(async () => { await loadPricing() }) beforeEach(() => { - originalConfigDir = process.env['CLAUDE_CONFIG_DIR'] setLocalModelSavings({}) setModelAliases({}) }) afterEach(async () => { delete (Object.prototype as Record).calls - if (originalConfigDir === undefined) { - delete process.env['CLAUDE_CONFIG_DIR'] - } else { - process.env['CLAUDE_CONFIG_DIR'] = originalConfigDir - } clearSessionCache() while (tmpDirs.length > 0) { const d = tmpDirs.pop() diff --git a/tests/parser-proxy-codex-only.test.ts b/tests/parser-proxy-codex-only.test.ts index 9c59949a..1bb92648 100644 --- a/tests/parser-proxy-codex-only.test.ts +++ b/tests/parser-proxy-codex-only.test.ts @@ -18,8 +18,6 @@ const CWD = '/Users/test/codexonlyproxied' let tmpDirs: string[] = [] afterEach(async () => { - delete process.env['CODEX_HOME'] - delete process.env['CLAUDE_CONFIG_DIR'] while (tmpDirs.length > 0) { const d = tmpDirs.pop() if (d) await rm(d, { recursive: true, force: true }) diff --git a/tests/parser-proxy-merge.test.ts b/tests/parser-proxy-merge.test.ts index 19dd3c85..a0d8dc95 100644 --- a/tests/parser-proxy-merge.test.ts +++ b/tests/parser-proxy-merge.test.ts @@ -19,8 +19,6 @@ const MERGE_CWD = '/Users/test/proxiedmerge' let tmpDirs: string[] = [] afterEach(async () => { - delete process.env['CLAUDE_CONFIG_DIR'] - delete process.env['CODEX_HOME'] while (tmpDirs.length > 0) { const d = tmpDirs.pop() if (d) await rm(d, { recursive: true, force: true }) diff --git a/tests/parser-proxy-pricing.test.ts b/tests/parser-proxy-pricing.test.ts index 10cf768e..26df51b1 100644 --- a/tests/parser-proxy-pricing.test.ts +++ b/tests/parser-proxy-pricing.test.ts @@ -127,14 +127,12 @@ const makeRange = (): DateRange => ({ start: RANGE_START, end: RANGE_END }) const FIXTURE_CWD = '/private/var/eywa-proxy-fixture/acme' let tmpDirs: string[] = [] -let originalConfigDir: string | undefined beforeAll(async () => { await loadPricing() }) beforeEach(() => { - originalConfigDir = process.env['CLAUDE_CONFIG_DIR'] setProxyPaths([]) setLocalModelSavings({}) setModelAliases({}) @@ -143,8 +141,6 @@ beforeEach(() => { afterEach(async () => { setProxyPaths([]) - if (originalConfigDir === undefined) delete process.env['CLAUDE_CONFIG_DIR'] - else process.env['CLAUDE_CONFIG_DIR'] = originalConfigDir clearSessionCache() while (tmpDirs.length > 0) { const d = tmpDirs.pop() diff --git a/tests/parser.test.ts b/tests/parser.test.ts index 46edeb42..1a2a4ece 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -172,15 +172,11 @@ function totalOutput(projects: Awaited>): nu // ── Common env setup ────────────────────────────────────────────────────── let tmpHome: string let tmpCache: string -let prevHome: string | undefined -let prevCache: string | undefined beforeEach(async () => { tmpHome = await mkdtemp(join(tmpdir(), 'cb-parser-test-home-')) tmpCache = await mkdtemp(join(tmpdir(), 'cb-parser-test-cache-')) - prevHome = process.env['HOME'] - prevCache = process.env['CODEBURN_CACHE_DIR'] process.env['HOME'] = tmpHome process.env['CODEBURN_CACHE_DIR'] = tmpCache @@ -194,11 +190,6 @@ afterEach(async () => { clearSessionCache() vi.unstubAllEnvs() - if (prevHome === undefined) delete process.env['HOME'] - else process.env['HOME'] = prevHome - if (prevCache === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = prevCache - _synthSources = [] await rm(tmpHome, { recursive: true, force: true }) diff --git a/tests/plan-usage.test.ts b/tests/plan-usage.test.ts index b8a81c61..eabfe5a5 100644 --- a/tests/plan-usage.test.ts +++ b/tests/plan-usage.test.ts @@ -185,7 +185,6 @@ describe('getPlanUsage', () => { it('keeps the provider-specific parser filter for one active plan', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-plan-usage-test-')) - const previousHome = process.env['HOME'] process.env['HOME'] = dir try { @@ -217,18 +216,12 @@ describe('getPlanUsage', () => { expect(usages).toHaveLength(1) expect(usages[0]?.spentApiEquivalentUsd).toBe(80) } finally { - if (previousHome === undefined) { - delete process.env['HOME'] - } else { - process.env['HOME'] = previousHome - } await rm(dir, { recursive: true, force: true }) } }) it('computes multiple active plan usages from one all-provider parse', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-plan-usage-test-')) - const previousHome = process.env['HOME'] process.env['HOME'] = dir try { @@ -344,11 +337,6 @@ describe('getPlanUsage', () => { expect(usages.map(usage => usage.plan.provider)).toEqual(['claude', 'codex']) expect(usages.map(usage => usage.spentApiEquivalentUsd)).toEqual([100, 50]) } finally { - if (previousHome === undefined) { - delete process.env['HOME'] - } else { - process.env['HOME'] = previousHome - } await rm(dir, { recursive: true, force: true }) } }) diff --git a/tests/plans.test.ts b/tests/plans.test.ts index 4ed7cc86..ca4553b3 100644 --- a/tests/plans.test.ts +++ b/tests/plans.test.ts @@ -29,7 +29,6 @@ describe('plan presets', () => { describe('plan config persistence', () => { it('round-trips per-provider plans and clears one provider at a time', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-plan-test-')) - const previousHome = process.env['HOME'] process.env['HOME'] = dir try { @@ -74,18 +73,12 @@ describe('plan config persistence', () => { expect(await readPlan()).toBeUndefined() expect(await readPlans()).toEqual({}) } finally { - if (previousHome === undefined) { - delete process.env['HOME'] - } else { - process.env['HOME'] = previousHome - } await rm(dir, { recursive: true, force: true }) } }) it('reads legacy single-plan config as a provider-keyed plan map', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-plan-test-')) - const previousHome = process.env['HOME'] process.env['HOME'] = dir try { @@ -107,18 +100,12 @@ describe('plan config persistence', () => { resetDay: 3, }) } finally { - if (previousHome === undefined) { - delete process.env['HOME'] - } else { - process.env['HOME'] = previousHome - } await rm(dir, { recursive: true, force: true }) } }) it('drops a hand-edited all plan when provider-specific plans are present', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-plan-test-')) - const previousHome = process.env['HOME'] process.env['HOME'] = dir try { @@ -144,18 +131,12 @@ describe('plan config persistence', () => { expect(plans.claude).toMatchObject({ id: 'claude-max', provider: 'claude' }) expect(await readPlan()).toMatchObject({ id: 'claude-max', provider: 'claude' }) } finally { - if (previousHome === undefined) { - delete process.env['HOME'] - } else { - process.env['HOME'] = previousHome - } await rm(dir, { recursive: true, force: true }) } }) it('does not allow an all-provider plan to overlap provider-specific plans', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-plan-test-')) - const previousHome = process.env['HOME'] process.env['HOME'] = dir try { @@ -191,11 +172,6 @@ describe('plan config persistence', () => { }) expect((await readPlans()).claude).toBeUndefined() } finally { - if (previousHome === undefined) { - delete process.env['HOME'] - } else { - process.env['HOME'] = previousHome - } await rm(dir, { recursive: true, force: true }) } }) diff --git a/tests/provider-turn-grouping.test.ts b/tests/provider-turn-grouping.test.ts index 27ff5a7a..d9dd0d65 100644 --- a/tests/provider-turn-grouping.test.ts +++ b/tests/provider-turn-grouping.test.ts @@ -8,18 +8,12 @@ import type { DateRange } from '../src/types.js' let home: string let cacheDir: string let vibeHome: string -let originalHome: string | undefined -let originalCacheDir: string | undefined -let originalVibeHome: string | undefined let clearParserCache: (() => void) | undefined beforeEach(async () => { home = await mkdtemp(join(tmpdir(), 'codeburn-turn-group-home-')) cacheDir = await mkdtemp(join(tmpdir(), 'codeburn-turn-group-cache-')) vibeHome = await mkdtemp(join(tmpdir(), 'codeburn-turn-group-vibe-')) - originalHome = process.env['HOME'] - originalCacheDir = process.env['CODEBURN_CACHE_DIR'] - originalVibeHome = process.env['VIBE_HOME'] process.env['HOME'] = home process.env['CODEBURN_CACHE_DIR'] = cacheDir process.env['VIBE_HOME'] = vibeHome @@ -29,12 +23,6 @@ afterEach(async () => { clearParserCache?.() clearParserCache = undefined vi.resetModules() - if (originalHome === undefined) delete process.env['HOME'] - else process.env['HOME'] = originalHome - if (originalCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = originalCacheDir - if (originalVibeHome === undefined) delete process.env['VIBE_HOME'] - else process.env['VIBE_HOME'] = originalVibeHome await rm(home, { recursive: true, force: true }) await rm(cacheDir, { recursive: true, force: true }) await rm(vibeHome, { recursive: true, force: true }) diff --git a/tests/providers/antigravity.test.ts b/tests/providers/antigravity.test.ts index 088c873f..76cba7be 100644 --- a/tests/providers/antigravity.test.ts +++ b/tests/providers/antigravity.test.ts @@ -263,7 +263,6 @@ describe('antigravity provider helpers', () => { it('captures exact Antigravity CLI statusLine usage as fallback calls', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-antigravity-statusline-')) - const oldCacheDir = process.env['CODEBURN_CACHE_DIR'] process.env['CODEBURN_CACHE_DIR'] = dir try { @@ -317,15 +316,12 @@ describe('antigravity provider helpers', () => { expect(calls[0]!.projectPath).toBeUndefined() expect(calls[0]!.costUSD).toBeGreaterThan(0) } finally { - if (oldCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = oldCacheDir await rm(dir, { recursive: true, force: true }) } }) it('skips statusLine fallback calls when RPC cache already covered the conversation', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-antigravity-statusline-rpc-dedup-')) - const oldCacheDir = process.env['CODEBURN_CACHE_DIR'] process.env['CODEBURN_CACHE_DIR'] = dir try { @@ -354,15 +350,12 @@ describe('antigravity provider helpers', () => { expect(calls).toEqual([]) } finally { - if (oldCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = oldCacheDir await rm(dir, { recursive: true, force: true }) } }) it('skips singleton statusLine snapshots and deltas monotonic usage', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-antigravity-statusline-runs-')) - const oldCacheDir = process.env['CODEBURN_CACHE_DIR'] process.env['CODEBURN_CACHE_DIR'] = dir const basePayload = { @@ -409,15 +402,12 @@ describe('antigravity provider helpers', () => { ]) expect(calls.map(call => call.cachedInputTokens)).toEqual([0, 0]) } finally { - if (oldCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = oldCacheDir await rm(dir, { recursive: true, force: true }) } }) it('treats non-monotonic statusLine usage as a new request snapshot', async () => { const dir = await mkdtemp(join(tmpdir(), 'codeburn-antigravity-statusline-reset-')) - const oldCacheDir = process.env['CODEBURN_CACHE_DIR'] process.env['CODEBURN_CACHE_DIR'] = dir const payload = ( @@ -458,8 +448,6 @@ describe('antigravity provider helpers', () => { [200, 30, 500], ]) } finally { - if (oldCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR'] - else process.env['CODEBURN_CACHE_DIR'] = oldCacheDir await rm(dir, { recursive: true, force: true }) } }) diff --git a/tests/providers/crush.test.ts b/tests/providers/crush.test.ts index 4835f6f3..b86f5358 100644 --- a/tests/providers/crush.test.ts +++ b/tests/providers/crush.test.ts @@ -18,19 +18,12 @@ type TestDb = { } let tmpRoot: string -let originalEnv: string | undefined beforeEach(async () => { tmpRoot = await mkdtemp(join(tmpdir(), 'crush-test-')) - originalEnv = process.env['CRUSH_GLOBAL_DATA'] }) afterEach(async () => { - if (originalEnv === undefined) { - delete process.env['CRUSH_GLOBAL_DATA'] - } else { - process.env['CRUSH_GLOBAL_DATA'] = originalEnv - } await rm(tmpRoot, { recursive: true, force: true }) }) diff --git a/tests/providers/cursor-large-db-cap.test.ts b/tests/providers/cursor-large-db-cap.test.ts index b5b0256e..7b60c3d9 100644 --- a/tests/providers/cursor-large-db-cap.test.ts +++ b/tests/providers/cursor-large-db-cap.test.ts @@ -18,16 +18,12 @@ import type { DateRange } from '../../src/types.js' const skipReason = isSqliteAvailable() ? null : 'node:sqlite not available — needs Node 22+; skipping' let tmpDir: string -let savedBudget: string | undefined beforeEach(async () => { tmpDir = await mkdtemp(join(tmpdir(), 'cursor-cap-')) - savedBudget = process.env['CODEBURN_CURSOR_MAX_BUBBLES'] }) afterEach(async () => { - if (savedBudget === undefined) delete process.env['CODEBURN_CURSOR_MAX_BUBBLES'] - else process.env['CODEBURN_CURSOR_MAX_BUBBLES'] = savedBudget await rm(tmpDir, { recursive: true, force: true }) }) diff --git a/tests/providers/devin.test.ts b/tests/providers/devin.test.ts index 5197140f..4efbe982 100644 --- a/tests/providers/devin.test.ts +++ b/tests/providers/devin.test.ts @@ -8,7 +8,6 @@ import { createDevinProvider } from '../../src/providers/devin.js' import type { ParsedProviderCall } from '../../src/providers/types.js' let tmpDir: string -const originalHome = process.env['HOME'] beforeEach(async () => { tmpDir = await mkdtemp(join(tmpdir(), 'devin-provider-')) @@ -16,8 +15,6 @@ beforeEach(async () => { }) afterEach(async () => { - if (originalHome === undefined) delete process.env['HOME'] - else process.env['HOME'] = originalHome await rm(tmpDir, { recursive: true, force: true }) }) diff --git a/tests/providers/mistral-vibe.test.ts b/tests/providers/mistral-vibe.test.ts index b8d49bec..1a393fc6 100644 --- a/tests/providers/mistral-vibe.test.ts +++ b/tests/providers/mistral-vibe.test.ts @@ -7,20 +7,12 @@ import { createMistralVibeProvider } from '../../src/providers/mistral-vibe.js' import type { ParsedProviderCall } from '../../src/providers/types.js' let tmpDir: string -let originalVibeHome: string | undefined beforeEach(async () => { tmpDir = await mkdtemp(join(tmpdir(), 'mistral-vibe-test-')) - originalVibeHome = process.env['VIBE_HOME'] - delete process.env['VIBE_HOME'] }) afterEach(async () => { - if (originalVibeHome === undefined) { - delete process.env['VIBE_HOME'] - } else { - process.env['VIBE_HOME'] = originalVibeHome - } await rm(tmpDir, { recursive: true, force: true }) }) diff --git a/tests/security/prototype-pollution.test.ts b/tests/security/prototype-pollution.test.ts index 6b6075c4..8a57ac20 100644 --- a/tests/security/prototype-pollution.test.ts +++ b/tests/security/prototype-pollution.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, afterEach, beforeEach } from 'vitest' +import { describe, it, expect, afterEach } from 'vitest' import { mkdtemp, mkdir, cp, rm } from 'fs/promises' import { tmpdir } from 'os' import { join } from 'path' @@ -28,19 +28,9 @@ function makeRange(offsetMs: number): DateRange { describe('HIGH-1 prototype pollution via unchecked bracket-assign', () => { const tmpDirs: string[] = [] - let originalConfigDir: string | undefined - - beforeEach(() => { - originalConfigDir = process.env['CLAUDE_CONFIG_DIR'] - }) afterEach(async () => { delete (Object.prototype as Record).calls - if (originalConfigDir === undefined) { - delete process.env['CLAUDE_CONFIG_DIR'] - } else { - process.env['CLAUDE_CONFIG_DIR'] = originalConfigDir - } while (tmpDirs.length > 0) { const d = tmpDirs.pop() if (d) await rm(d, { recursive: true, force: true }) diff --git a/tests/session-cache.test.ts b/tests/session-cache.test.ts index a8f4eeb4..b015322b 100644 --- a/tests/session-cache.test.ts +++ b/tests/session-cache.test.ts @@ -28,7 +28,6 @@ beforeEach(() => { }) afterEach(async () => { - delete process.env['CODEBURN_CACHE_DIR'] if (existsSync(TMP_DIR)) await rm(TMP_DIR, { recursive: true }) }) @@ -171,11 +170,8 @@ describe('computeEnvFingerprint', () => { it('changes when env var changes', () => { const before = computeEnvFingerprint('claude') - const orig = process.env['CLAUDE_CONFIG_DIR'] process.env['CLAUDE_CONFIG_DIR'] = '/tmp/different' const after = computeEnvFingerprint('claude') - if (orig === undefined) delete process.env['CLAUDE_CONFIG_DIR'] - else process.env['CLAUDE_CONFIG_DIR'] = orig expect(before).not.toBe(after) }) diff --git a/tests/setup/env-isolation.ts b/tests/setup/env-isolation.ts new file mode 100644 index 00000000..079165db --- /dev/null +++ b/tests/setup/env-isolation.ts @@ -0,0 +1,110 @@ +// Vitest setup file: isolates every test from the developer's shell environment. +// +// codeburn discovers sessions through a long list of provider-specific env +// vars (CLAUDE_CONFIG_DIR, CODEX_HOME, CRUSH_GLOBAL_DATA, …) and via HOME / +// XDG_* / APPDATA / LOCALAPPDATA. Without this file, any value set in the +// developer's shell (e.g. CLAUDE_CONFIG_DIRS=/Users/me/.claude:…) bleeds into +// fixture-based tests: the parser reads the developer's REAL sessions instead +// of the temp-dir fixture, producing nonsense totals and false failures that +// pass on a clean CI runner. +// +// What this file does: +// 1. Mints an empty sandbox temp dir once per worker. +// 2. REDIRECTED vars (HOME / XDG_* / APPDATA / LOCALAPPDATA) point at the +// sandbox so any fallback to homedir() / platform defaults lands in an +// empty filesystem. +// 3. CLEARED vars (every provider's explicit override) are deleted so a test +// that does NOT set one gets "unconfigured" rather than the dev's value. +// 4. PRESERVED vars (PATH, COLUMNS, …) are snapshotted from the dev's shell +// and restored every test. We can't wipe them - Node uses PATH for spawn +// and module resolution, terminal code uses COLUMNS - but a test that +// mutates them shouldn't leak the change into the next test. +// 5. Re-asserts the above before EVERY test (global beforeEach), so a test +// that mutates an env var doesn't leak its value into the next test. +// Tests can freely set process.env['HOME'] = customDir without saving the +// previous value - the next test gets a fresh sandbox baseline. +// +// CAVEAT: env vars set in a test file's beforeAll() get overwritten by this +// file's beforeEach before each test runs. Use beforeEach (not beforeAll) when +// the test body depends on a specific env var value. + +import { mkdtempSync } from 'fs' +import { tmpdir } from 'os' +import { join } from 'path' +import { beforeEach } from 'vitest' + +const sandbox = mkdtempSync(join(tmpdir(), 'codeburn-test-env-')) + +const REDIRECTED = [ + 'HOME', + 'XDG_CONFIG_HOME', + 'XDG_DATA_HOME', + 'XDG_CACHE_HOME', + 'XDG_STATE_HOME', + 'APPDATA', + 'LOCALAPPDATA', +] as const + +const CLEARED = [ + // Provider session-discovery dirs + 'CLAUDE_CONFIG_DIR', + 'CLAUDE_CONFIG_DIRS', + 'CODEX_HOME', + 'CRUSH_GLOBAL_DATA', + 'CODEBUFF_DATA_DIR', + 'FACTORY_DIR', + 'GOOSE_PATH_ROOT', + 'GROK_HOME', + 'KIRO_HOME', + 'KIMI_SHARE_DIR', + 'MUX_ROOT', + 'QWEN_DATA_DIR', + 'VIBE_HOME', + 'WARP_DB_PATH', + 'ZS_DATA_DIR', + // codeburn override dirs / paths + 'CODEBURN_CACHE_DIR', + 'CODEBURN_COPILOT_OTEL_DB', + 'CODEBURN_COPILOT_SESSION_STATE_DIR', + 'CODEBURN_COPILOT_WS_STORAGE_DIR', + 'CODEBURN_DESKTOP_SESSIONS_DIR', + 'CODEBURN_MUX_DIR', + 'CODEBURN_ANTIGRAVITY_SETTINGS_PATH', + // codeburn behavior toggles (set by the dev to tweak local runs) + 'CODEBURN_COPILOT_DISABLE_OTEL', + 'CODEBURN_TZ', + 'CODEBURN_VERBOSE', + 'CODEBURN_CURSOR_MAX_BUBBLES', + 'CODEBURN_FORCE_MACOS_MAJOR', + // Provider model/credential overrides + 'KIMI_MODEL_NAME', + 'AI_GATEWAY_API_KEY', + 'VERCEL_OIDC_TOKEN', + // Read by detectBashBloat - a dev's real shell limit must not bleed in + 'BASH_MAX_OUTPUT_LENGTH', +] as const + +// Snapshotted from the dev's shell and restored every test. These can't be +// wiped (Node needs PATH for spawn / module resolution, dashboard/table layout +// reads COLUMNS) but a test that mutates them shouldn't leak. +const PRESERVED = ['PATH', 'COLUMNS'] as const +const preservedSnapshot = new Map() +for (const key of PRESERVED) preservedSnapshot.set(key, process.env[key]) + +function applyIsolation(): void { + for (const key of REDIRECTED) process.env[key] = sandbox + for (const key of CLEARED) delete process.env[key] + for (const key of PRESERVED) { + const original = preservedSnapshot.get(key) + if (original === undefined) delete process.env[key] + else process.env[key] = original + } + // Pin the timezone so date grouping is deterministic regardless of the dev's + // shell TZ. Clearing it is not enough (Node falls back to the OS zone); a + // non-UTC TZ would otherwise shift day buckets versus a clean CI runner. A + // test that needs a specific zone can still set process.env.TZ in beforeEach. + process.env.TZ = 'UTC' +} + +applyIsolation() +beforeEach(applyIsolation) diff --git a/tests/usage-aggregator.test.ts b/tests/usage-aggregator.test.ts index 8a577dc1..3a47c7fb 100644 --- a/tests/usage-aggregator.test.ts +++ b/tests/usage-aggregator.test.ts @@ -1,39 +1,13 @@ -import { describe, expect, it, beforeAll, afterAll } from 'vitest' -import { mkdtemp, rm } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { join } from 'node:path' +import { describe, expect, it, beforeAll } from 'vitest' import { buildMenubarPayloadForRange } from '../src/usage-aggregator.js' import { getDateRange } from '../src/cli-date.js' import { loadPricing } from '../src/models.js' describe('buildMenubarPayloadForRange', () => { - // Point HOME / config at an empty temp dir so the payload is built from an - // empty dataset. This keeps the test deterministic and fast regardless of how - // much real session data the developer's machine has for "today" (parsing a - // heavy day previously pushed this past its timeout). - const saved: Record = {} - let tmp: string - beforeAll(async () => { - tmp = await mkdtemp(join(tmpdir(), 'codeburn-agg-test-')) - for (const key of ['HOME', 'CLAUDE_CONFIG_DIR', 'XDG_CONFIG_HOME', 'CODEBURN_CACHE_DIR']) { - saved[key] = process.env[key] - } - process.env['HOME'] = tmp - process.env['CLAUDE_CONFIG_DIR'] = join(tmp, '.claude') - process.env['XDG_CONFIG_HOME'] = join(tmp, '.config') - process.env['CODEBURN_CACHE_DIR'] = join(tmp, 'cache') await loadPricing() }) - afterAll(async () => { - for (const [key, value] of Object.entries(saved)) { - if (value === undefined) delete process.env[key] - else process.env[key] = value - } - await rm(tmp, { recursive: true, force: true }) - }) - it('returns a valid payload and skips optimize findings when optimize:false', async () => { const payload = await buildMenubarPayloadForRange(getDateRange('today'), { provider: 'all', optimize: false }) expect(typeof payload.current.label).toBe('string') diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..b56c0158 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + // Runs once per worker before any test. Scrubs the developer's shell so + // session-discovery env vars (CLAUDE_CONFIG_DIRS, HOME, XDG_*, every + // provider-specific *_HOME) don't bleed real local data into fixtures. + setupFiles: ['./tests/setup/env-isolation.ts'], + }, +})