From 9f724c696005f038b3bf6bdee9dfe3d3f98f426e Mon Sep 17 00:00:00 2001 From: Jethro Yu Date: Fri, 31 Oct 2025 10:49:18 +0800 Subject: [PATCH 1/3] fix: resolve test conflicts with .env environment variables - Add Jest global setup/teardown hooks to backup and restore .env file - Clear OCO_ environment variables during test execution - Modify test resetEnv function to prevent OCO_ env vars from interfering - Prevent .env file from interfering with test expectations This fixes the issue where tests were failing due to environment variables from the user's .env file overriding the expected default config values. --- jest.config.ts | 2 ++ test/jest-global-setup.ts | 20 ++++++++++++++++++++ test/jest-global-teardown.ts | 13 +++++++++++++ test/unit/config.test.ts | 5 ++++- 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/jest-global-setup.ts create mode 100644 test/jest-global-teardown.ts diff --git a/jest.config.ts b/jest.config.ts index bd828222..baeeea72 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -11,6 +11,8 @@ const config: Config = { moduleDirectories: ['node_modules', 'src'], preset: 'ts-jest/presets/default-esm', setupFilesAfterEnv: ['/test/jest-setup.ts'], + globalSetup: '/test/jest-global-setup.ts', + globalTeardown: '/test/jest-global-teardown.ts', testEnvironment: 'node', testRegex: ['.*\\.test\\.ts$'], // Tell Jest to ignore the specific duplicate package.json files diff --git a/test/jest-global-setup.ts b/test/jest-global-setup.ts new file mode 100644 index 00000000..c0efca91 --- /dev/null +++ b/test/jest-global-setup.ts @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); + +module.exports = async function globalSetup() { + const envPath = path.join(process.cwd(), '.env'); + const backupPath = path.join(process.cwd(), '.env.test-backup'); + + // Backup .env file if it exists and clear related env vars + if (fs.existsSync(envPath)) { + fs.renameSync(envPath, backupPath); + console.log('Backed up .env file for testing'); + + // Also clear any OCO_ environment variables that might have been loaded + Object.keys(process.env).forEach(key => { + if (key.startsWith('OCO_')) { + delete process.env[key]; + } + }); + } +} diff --git a/test/jest-global-teardown.ts b/test/jest-global-teardown.ts new file mode 100644 index 00000000..5f741a48 --- /dev/null +++ b/test/jest-global-teardown.ts @@ -0,0 +1,13 @@ +const fs = require('fs'); +const path = require('path'); + +module.exports = async function globalTeardown() { + const envPath = path.join(process.cwd(), '.env'); + const backupPath = path.join(process.cwd(), '.env.test-backup'); + + // Restore .env file + if (fs.existsSync(backupPath)) { + fs.renameSync(backupPath, envPath); + console.log('Restored .env file after testing'); + } +} diff --git a/test/unit/config.test.ts b/test/unit/config.test.ts index 448944ed..ae56a55f 100644 --- a/test/unit/config.test.ts +++ b/test/unit/config.test.ts @@ -15,7 +15,10 @@ describe('config', () => { function resetEnv(env: NodeJS.ProcessEnv) { Object.keys(process.env).forEach((key) => { - if (!(key in env)) { + if (key.startsWith('OCO_')) { + // Don't restore OCO_ environment variables to avoid test interference + delete process.env[key]; + } else if (!(key in env)) { delete process.env[key]; } else { process.env[key] = env[key]; From 2073a37d911ea32d8be210dd0adc86bf256704ad Mon Sep 17 00:00:00 2001 From: Jethro Yu Date: Fri, 4 Sep 2026 15:24:09 +0800 Subject: [PATCH 2/3] fix(test): isolate config environment --- jest.config.ts | 2 -- test/jest-global-setup.ts | 20 -------------------- test/jest-global-teardown.ts | 13 ------------- test/unit/config.test.ts | 17 ++++++----------- 4 files changed, 6 insertions(+), 46 deletions(-) delete mode 100644 test/jest-global-setup.ts delete mode 100644 test/jest-global-teardown.ts diff --git a/jest.config.ts b/jest.config.ts index baeeea72..bd828222 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -11,8 +11,6 @@ const config: Config = { moduleDirectories: ['node_modules', 'src'], preset: 'ts-jest/presets/default-esm', setupFilesAfterEnv: ['/test/jest-setup.ts'], - globalSetup: '/test/jest-global-setup.ts', - globalTeardown: '/test/jest-global-teardown.ts', testEnvironment: 'node', testRegex: ['.*\\.test\\.ts$'], // Tell Jest to ignore the specific duplicate package.json files diff --git a/test/jest-global-setup.ts b/test/jest-global-setup.ts deleted file mode 100644 index c0efca91..00000000 --- a/test/jest-global-setup.ts +++ /dev/null @@ -1,20 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -module.exports = async function globalSetup() { - const envPath = path.join(process.cwd(), '.env'); - const backupPath = path.join(process.cwd(), '.env.test-backup'); - - // Backup .env file if it exists and clear related env vars - if (fs.existsSync(envPath)) { - fs.renameSync(envPath, backupPath); - console.log('Backed up .env file for testing'); - - // Also clear any OCO_ environment variables that might have been loaded - Object.keys(process.env).forEach(key => { - if (key.startsWith('OCO_')) { - delete process.env[key]; - } - }); - } -} diff --git a/test/jest-global-teardown.ts b/test/jest-global-teardown.ts deleted file mode 100644 index 5f741a48..00000000 --- a/test/jest-global-teardown.ts +++ /dev/null @@ -1,13 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -module.exports = async function globalTeardown() { - const envPath = path.join(process.cwd(), '.env'); - const backupPath = path.join(process.cwd(), '.env.test-backup'); - - // Restore .env file - if (fs.existsSync(backupPath)) { - fs.renameSync(backupPath, envPath); - console.log('Restored .env file after testing'); - } -} diff --git a/test/unit/config.test.ts b/test/unit/config.test.ts index ae56a55f..776806be 100644 --- a/test/unit/config.test.ts +++ b/test/unit/config.test.ts @@ -10,24 +10,19 @@ import { dirname } from 'path'; describe('config', () => { const originalEnv = { ...process.env }; + const testEnv = Object.fromEntries( + Object.entries(originalEnv).filter(([key]) => !key.startsWith('OCO_')) + ); let globalConfigFile: { filePath: string; cleanup: () => Promise }; let envConfigFile: { filePath: string; cleanup: () => Promise }; function resetEnv(env: NodeJS.ProcessEnv) { - Object.keys(process.env).forEach((key) => { - if (key.startsWith('OCO_')) { - // Don't restore OCO_ environment variables to avoid test interference - delete process.env[key]; - } else if (!(key in env)) { - delete process.env[key]; - } else { - process.env[key] = env[key]; - } - }); + Object.keys(process.env).forEach((key) => delete process.env[key]); + Object.assign(process.env, env); } beforeEach(async () => { - resetEnv(originalEnv); + resetEnv(testEnv); if (globalConfigFile) await globalConfigFile.cleanup(); if (envConfigFile) await envConfigFile.cleanup(); }); From e4ac84faf1957da5753337c766be557b683eb27c Mon Sep 17 00:00:00 2001 From: di-sukharev Date: Wed, 9 Sep 2026 18:28:47 +0300 Subject: [PATCH 3/3] test(config): isolate default dotenv loading --- test/unit/config.test.ts | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/test/unit/config.test.ts b/test/unit/config.test.ts index 776806be..5522b6cf 100644 --- a/test/unit/config.test.ts +++ b/test/unit/config.test.ts @@ -1,13 +1,24 @@ import { existsSync, readFileSync, rmSync } from 'fs'; -import { - CONFIG_KEYS, - DEFAULT_CONFIG, - getConfig, - setConfig -} from '../../src/commands/config'; +import { jest } from '@jest/globals'; import { prepareFile } from './utils'; import { dirname } from 'path'; +const defaultEnvFile = await prepareFile('.env', ''); +afterAll(() => defaultEnvFile.cleanup()); + +const { CONFIG_KEYS, DEFAULT_CONFIG, configValidators, getConfig, setConfig } = + await (async () => { + // The config module captures its default .env path on import, including the + // path used internally by setConfig. Point it at a fixture for this suite. + const originalCwd = process.cwd(); + try { + process.chdir(dirname(defaultEnvFile.filePath)); + return await import('../../src/commands/config'); + } finally { + process.chdir(originalCwd); + } + })(); + describe('config', () => { const originalEnv = { ...process.env }; const testEnv = Object.fromEntries( @@ -425,10 +436,19 @@ describe('config', () => { true, Number.MAX_SAFE_INTEGER + 1 ]; - for (const val of invalidValues) { - expect(() => - configValidators[CONFIG_KEYS.OCO_REASONING_MAX_TOKENS](val) - ).toThrow(); + const exit = jest.spyOn(process, 'exit').mockImplementation((code) => { + throw new Error(`process.exit(${code})`); + }); + try { + for (const val of invalidValues) { + exit.mockClear(); + expect(() => + configValidators[CONFIG_KEYS.OCO_REASONING_MAX_TOKENS](val) + ).toThrow('process.exit(1)'); + expect(exit).toHaveBeenCalledWith(1); + } + } finally { + exit.mockRestore(); } }); });