import { existsSync, readFileSync, rmSync } from 'fs'; import { CONFIG_KEYS, DEFAULT_CONFIG, getConfig, setConfig } from '../../src/commands/config'; import { prepareFile } from './utils'; import { dirname } from 'path'; describe('config', () => { const originalEnv = { ...process.env }; 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 in env)) { delete process.env[key]; } else { process.env[key] = env[key]; } }); } beforeEach(async () => { resetEnv(originalEnv); if (globalConfigFile) await globalConfigFile.cleanup(); if (envConfigFile) await envConfigFile.cleanup(); }); afterEach(async () => { if (globalConfigFile) await globalConfigFile.cleanup(); if (envConfigFile) await envConfigFile.cleanup(); }); afterAll(() => { resetEnv(originalEnv); }); const generateConfig = async ( fileName: string, content: Record ) => { const fileContent = Object.entries(content) .map(([key, value]) => `${key}="${value}"`) .join('\n'); return await prepareFile(fileName, fileContent); }; describe('getConfig', () => { it('should prioritize local .env over global .opencommit config', async () => { globalConfigFile = await generateConfig('.opencommit', { OCO_API_KEY: 'global-key', OCO_MODEL: 'gpt-3.5-turbo', OCO_LANGUAGE: 'en' }); envConfigFile = await generateConfig('.env', { OCO_API_KEY: 'local-key', OCO_LANGUAGE: 'fr' }); const config = getConfig({ globalPath: globalConfigFile.filePath, envPath: envConfigFile.filePath }); expect(config).not.toEqual(null); expect(config.OCO_API_KEY).toEqual('local-key'); expect(config.OCO_MODEL).toEqual('gpt-3.5-turbo'); expect(config.OCO_LANGUAGE).toEqual('fr'); }); it('should fallback to global config when local config is not set', async () => { globalConfigFile = await generateConfig('.opencommit', { OCO_API_KEY: 'global-key', OCO_MODEL: 'gpt-4', OCO_LANGUAGE: 'de', OCO_DESCRIPTION: 'true' }); envConfigFile = await generateConfig('.env', { OCO_API_URL: 'local-api-url' }); const config = getConfig({ globalPath: globalConfigFile.filePath, envPath: envConfigFile.filePath }); expect(config).not.toEqual(null); expect(config.OCO_API_KEY).toEqual('global-key'); expect(config.OCO_API_URL).toEqual('local-api-url'); expect(config.OCO_MODEL).toEqual('gpt-4'); expect(config.OCO_LANGUAGE).toEqual('de'); expect(config.OCO_DESCRIPTION).toEqual(true); }); it('should handle boolean and numeric values correctly', async () => { globalConfigFile = await generateConfig('.opencommit', { OCO_TOKENS_MAX_INPUT: '4096', OCO_TOKENS_MAX_OUTPUT: '500', OCO_GITPUSH: 'true' }); envConfigFile = await generateConfig('.env', { OCO_TOKENS_MAX_INPUT: '8192', OCO_ONE_LINE_COMMIT: 'false' }); const config = getConfig({ globalPath: globalConfigFile.filePath, envPath: envConfigFile.filePath }); expect(config).not.toEqual(null); expect(config.OCO_TOKENS_MAX_INPUT).toEqual(8192); expect(config.OCO_TOKENS_MAX_OUTPUT).toEqual(500); expect(config.OCO_GITPUSH).toEqual(true); expect(config.OCO_ONE_LINE_COMMIT).toEqual(false); }); it('should handle empty local config correctly', async () => { globalConfigFile = await generateConfig('.opencommit', { OCO_API_KEY: 'global-key', OCO_MODEL: 'gpt-4', OCO_LANGUAGE: 'es' }); envConfigFile = await generateConfig('.env', {}); const config = getConfig({ globalPath: globalConfigFile.filePath, envPath: envConfigFile.filePath }); expect(config).not.toEqual(null); expect(config.OCO_API_KEY).toEqual('global-key'); expect(config.OCO_MODEL).toEqual('gpt-4'); expect(config.OCO_LANGUAGE).toEqual('es'); }); it('should override global config with null values in local .env', async () => { globalConfigFile = await generateConfig('.opencommit', { OCO_API_KEY: 'global-key', OCO_MODEL: 'gpt-4', OCO_LANGUAGE: 'es' }); envConfigFile = await generateConfig('.env', { OCO_API_KEY: 'null' }); const config = getConfig({ globalPath: globalConfigFile.filePath, envPath: envConfigFile.filePath }); expect(config).not.toEqual(null); expect(config.OCO_API_KEY).toEqual(null); }); it('should handle empty global config', async () => { globalConfigFile = await generateConfig('.opencommit', {}); envConfigFile = await generateConfig('.env', {}); const config = getConfig({ globalPath: globalConfigFile.filePath, envPath: envConfigFile.filePath }); expect(config).not.toEqual(null); expect(config.OCO_API_KEY).toEqual(undefined); }); }); describe('setConfig', () => { beforeEach(async () => { // we create and delete the file to have the parent directory, but not the file, to test the creation of the file globalConfigFile = await generateConfig('.opencommit', {}); rmSync(globalConfigFile.filePath); }); it('should create .opencommit file with DEFAULT CONFIG if it does not exist on first setConfig run', async () => { const isGlobalConfigFileExist = existsSync(globalConfigFile.filePath); expect(isGlobalConfigFileExist).toBe(false); await setConfig( [[CONFIG_KEYS.OCO_API_KEY, 'persisted-key_1']], globalConfigFile.filePath ); const fileContent = readFileSync(globalConfigFile.filePath, 'utf8'); expect(fileContent).toContain('OCO_API_KEY=persisted-key_1'); Object.entries(DEFAULT_CONFIG).forEach(([key, value]) => { expect(fileContent).toContain(`${key}=${value}`); }); }); it('should set new config values', async () => { globalConfigFile = await generateConfig('.opencommit', {}); await setConfig( [ [CONFIG_KEYS.OCO_API_KEY, 'new-key'], [CONFIG_KEYS.OCO_MODEL, 'gpt-4'] ], globalConfigFile.filePath ); const config = getConfig({ globalPath: globalConfigFile.filePath }); expect(config.OCO_API_KEY).toEqual('new-key'); expect(config.OCO_MODEL).toEqual('gpt-4'); }); it('should update existing config values', async () => { globalConfigFile = await generateConfig('.opencommit', { OCO_API_KEY: 'initial-key' }); await setConfig( [[CONFIG_KEYS.OCO_API_KEY, 'updated-key']], globalConfigFile.filePath ); const config = getConfig({ globalPath: globalConfigFile.filePath }); expect(config.OCO_API_KEY).toEqual('updated-key'); }); it('should handle boolean and numeric values correctly', async () => { globalConfigFile = await generateConfig('.opencommit', {}); await setConfig( [ [CONFIG_KEYS.OCO_TOKENS_MAX_INPUT, '8192'], [CONFIG_KEYS.OCO_DESCRIPTION, 'true'], [CONFIG_KEYS.OCO_ONE_LINE_COMMIT, 'false'] ], globalConfigFile.filePath ); const config = getConfig({ globalPath: globalConfigFile.filePath }); expect(config.OCO_TOKENS_MAX_INPUT).toEqual(8192); expect(config.OCO_DESCRIPTION).toEqual(true); expect(config.OCO_ONE_LINE_COMMIT).toEqual(false); }); it('should throw an error for unsupported config keys', async () => { globalConfigFile = await generateConfig('.opencommit', {}); try { await setConfig( [['UNSUPPORTED_KEY', 'value']], globalConfigFile.filePath ); throw new Error('NEVER_REACHED'); } catch (error) { expect(error.message).toContain( 'Unsupported config key: UNSUPPORTED_KEY' ); expect(error.message).not.toContain('NEVER_REACHED'); } }); it('should persist changes to the config file', async () => { const isGlobalConfigFileExist = existsSync(globalConfigFile.filePath); expect(isGlobalConfigFileExist).toBe(false); await setConfig( [[CONFIG_KEYS.OCO_API_KEY, 'persisted-key']], globalConfigFile.filePath ); const fileContent = readFileSync(globalConfigFile.filePath, 'utf8'); expect(fileContent).toContain('OCO_API_KEY=persisted-key'); }); it('should set multiple configs in a row and keep the changes', async () => { const isGlobalConfigFileExist = existsSync(globalConfigFile.filePath); expect(isGlobalConfigFileExist).toBe(false); await setConfig( [[CONFIG_KEYS.OCO_API_KEY, 'persisted-key']], globalConfigFile.filePath ); const fileContent1 = readFileSync(globalConfigFile.filePath, 'utf8'); expect(fileContent1).toContain('OCO_API_KEY=persisted-key'); await setConfig( [[CONFIG_KEYS.OCO_MODEL, 'gpt-4']], globalConfigFile.filePath ); const fileContent2 = readFileSync(globalConfigFile.filePath, 'utf8'); expect(fileContent2).toContain('OCO_MODEL=gpt-4'); }); }); });