import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { loadConfig, type HindsightConfig } from './config.js'; describe('loadConfig', () => { const originalEnv = { ...process.env }; beforeEach(() => { // Clear all HINDSIGHT_ env vars for (const key of Object.keys(process.env)) { if (key.startsWith('HINDSIGHT_')) { delete process.env[key]; } } }); afterEach(() => { process.env = { ...originalEnv }; }); it('returns defaults when no config sources exist', () => { const config = loadConfig(); expect(config.autoRecall).toBe(true); expect(config.autoRetain).toBe(true); expect(config.recallBudget).toBe('mid'); expect(config.recallMaxTokens).toBe(1024); expect(config.retainContext).toBe('opencode'); expect(config.agentName).toBe('opencode'); expect(config.dynamicBankId).toBe(false); expect(config.debug).toBe(false); expect(config.hindsightApiUrl).toBeNull(); expect(config.hindsightApiToken).toBeNull(); expect(config.bankId).toBeNull(); }); it('env vars override defaults', () => { process.env.HINDSIGHT_API_URL = 'https://example.com'; process.env.HINDSIGHT_API_TOKEN = 'secret-token'; process.env.HINDSIGHT_BANK_ID = 'my-bank'; process.env.HINDSIGHT_AUTO_RECALL = 'false'; process.env.HINDSIGHT_AUTO_RETAIN = '0'; process.env.HINDSIGHT_RECALL_MAX_TOKENS = '2048'; process.env.HINDSIGHT_DEBUG = 'true'; const config = loadConfig(); expect(config.hindsightApiUrl).toBe('https://example.com'); expect(config.hindsightApiToken).toBe('secret-token'); expect(config.bankId).toBe('my-bank'); expect(config.autoRecall).toBe(false); expect(config.autoRetain).toBe(false); expect(config.recallMaxTokens).toBe(2048); expect(config.debug).toBe(true); }); it('plugin options override defaults', () => { const config = loadConfig({ bankId: 'plugin-bank', autoRecall: false, recallBudget: 'high', }); expect(config.bankId).toBe('plugin-bank'); expect(config.autoRecall).toBe(false); expect(config.recallBudget).toBe('high'); }); it('env vars override plugin options', () => { process.env.HINDSIGHT_BANK_ID = 'env-bank'; const config = loadConfig({ bankId: 'plugin-bank' }); expect(config.bankId).toBe('env-bank'); }); it('boolean env var parsing', () => { process.env.HINDSIGHT_AUTO_RECALL = 'true'; expect(loadConfig().autoRecall).toBe(true); process.env.HINDSIGHT_AUTO_RECALL = '1'; expect(loadConfig().autoRecall).toBe(true); process.env.HINDSIGHT_AUTO_RECALL = 'yes'; expect(loadConfig().autoRecall).toBe(true); process.env.HINDSIGHT_AUTO_RECALL = 'false'; expect(loadConfig().autoRecall).toBe(false); process.env.HINDSIGHT_AUTO_RECALL = 'no'; expect(loadConfig().autoRecall).toBe(false); }); it('integer env var parsing', () => { process.env.HINDSIGHT_RECALL_MAX_TOKENS = '4096'; expect(loadConfig().recallMaxTokens).toBe(4096); // Invalid integer keeps default process.env.HINDSIGHT_RECALL_MAX_TOKENS = 'not-a-number'; expect(loadConfig().recallMaxTokens).toBe(1024); }); it('null plugin options are ignored', () => { const config = loadConfig({ bankId: null, debug: undefined }); expect(config.bankId).toBeNull(); // stays default null expect(config.debug).toBe(false); // stays default }); it('invalid retainMode falls back to full-session with warning', () => { const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); const config = loadConfig({ retainMode: 'full_session' }); expect(config.retainMode).toBe('full-session'); expect(spy).toHaveBeenCalledWith(expect.stringContaining('Unknown retainMode')); spy.mockRestore(); }); it('invalid recallBudget falls back to mid with warning', () => { const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); const config = loadConfig({ recallBudget: 'maximum' }); expect(config.recallBudget).toBe('mid'); expect(spy).toHaveBeenCalledWith(expect.stringContaining('Unknown recallBudget')); spy.mockRestore(); }); it('valid retainMode and recallBudget pass without warning', () => { const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); const config = loadConfig({ retainMode: 'last-turn', recallBudget: 'high' }); expect(config.retainMode).toBe('last-turn'); expect(config.recallBudget).toBe('high'); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); });