import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs'; import { join } from 'path'; import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { ConfigManager } from '../src/config-manager'; import { getProviderConfig, resetConfig } from '../src/index'; import { EnvLoader } from '../src/loaders/env.loader'; import { FileLoader } from '../src/loaders/file.loader'; import { appConfigSchema } from '../src/schemas'; const TEST_DIR = join(__dirname, 'provider-tests'); describe('Provider Configuration Tests', () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(() => { // Save original environment originalEnv = { ...process.env }; // Reset config singleton resetConfig(); // Clean up test directory if (existsSync(TEST_DIR)) { rmSync(TEST_DIR, { recursive: true, force: true }); } mkdirSync(TEST_DIR, { recursive: true }); }); afterEach(() => { // Restore original environment process.env = originalEnv; // Clean up resetConfig(); if (existsSync(TEST_DIR)) { rmSync(TEST_DIR, { recursive: true, force: true }); } }); test('should load WebShare provider config from environment variables', async () => { // Set WebShare environment variables process.env.WEBSHARE_API_KEY = 'test-webshare-key'; process.env.WEBSHARE_API_URL = 'https://custom.webshare.io/api/v2/'; process.env.WEBSHARE_ENABLED = 'true'; const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.webshare).toBeDefined(); expect(config.webshare?.apiKey).toBe('test-webshare-key'); expect(config.webshare?.apiUrl).toBe('https://custom.webshare.io/api/v2/'); expect(config.webshare?.enabled).toBe(true); }); test('should load EOD provider config from environment variables', async () => { // Set EOD environment variables process.env.EOD_API_KEY = 'test-eod-key'; process.env.EOD_BASE_URL = 'https://custom.eod.com/api'; process.env.EOD_TIER = 'all-in-one'; process.env.EOD_ENABLED = 'true'; process.env.EOD_PRIORITY = '10'; const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.providers?.eod).toBeDefined(); expect(config.providers?.eod?.apiKey).toBe('test-eod-key'); expect(config.providers?.eod?.baseUrl).toBe('https://custom.eod.com/api'); expect(config.providers?.eod?.tier).toBe('all-in-one'); expect(config.providers?.eod?.enabled).toBe(true); expect(config.providers?.eod?.priority).toBe(10); }); test('should load Interactive Brokers provider config from environment variables', async () => { // Set IB environment variables process.env.IB_GATEWAY_HOST = 'ib-gateway.example.com'; process.env.IB_GATEWAY_PORT = '7497'; process.env.IB_CLIENT_ID = '123'; process.env.IB_ACCOUNT = 'DU123456'; process.env.IB_MARKET_DATA_TYPE = 'live'; process.env.IB_ENABLED = 'true'; process.env.IB_PRIORITY = '5'; const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.providers?.ib).toBeDefined(); expect(config.providers?.ib?.gateway.host).toBe('ib-gateway.example.com'); expect(config.providers?.ib?.gateway.port).toBe(7497); expect(config.providers?.ib?.gateway.clientId).toBe(123); expect(config.providers?.ib?.account).toBe('DU123456'); expect(config.providers?.ib?.marketDataType).toBe('live'); expect(config.providers?.ib?.enabled).toBe(true); expect(config.providers?.ib?.priority).toBe(5); }); test('should load QuoteMedia provider config from environment variables', async () => { // Set QM environment variables process.env.QM_USERNAME = 'test-qm-user'; process.env.QM_PASSWORD = 'test-qm-pass'; process.env.QM_BASE_URL = 'https://custom.quotemedia.com/api'; process.env.QM_WEBMASTER_ID = 'webmaster123'; process.env.QM_ENABLED = 'true'; process.env.QM_PRIORITY = '15'; const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.providers?.qm).toBeDefined(); expect(config.providers?.qm?.username).toBe('test-qm-user'); expect(config.providers?.qm?.password).toBe('test-qm-pass'); expect(config.providers?.qm?.baseUrl).toBe('https://custom.quotemedia.com/api'); expect(config.providers?.qm?.webmasterId).toBe('webmaster123'); expect(config.providers?.qm?.enabled).toBe(true); expect(config.providers?.qm?.priority).toBe(15); }); test('should load Yahoo Finance provider config from environment variables', async () => { // Set Yahoo environment variables process.env.YAHOO_BASE_URL = 'https://custom.yahoo.com/api'; process.env.YAHOO_COOKIE_JAR = 'false'; process.env.YAHOO_CRUMB = 'test-crumb'; process.env.YAHOO_ENABLED = 'true'; process.env.YAHOO_PRIORITY = '20'; const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.providers?.yahoo).toBeDefined(); expect(config.providers?.yahoo?.baseUrl).toBe('https://custom.yahoo.com/api'); expect(config.providers?.yahoo?.cookieJar).toBe(false); expect(config.providers?.yahoo?.crumb).toBe('test-crumb'); expect(config.providers?.yahoo?.enabled).toBe(true); expect(config.providers?.yahoo?.priority).toBe(20); }); test('should merge file config with environment variables', async () => { // Create a config file const configDir = join(TEST_DIR, 'config'); mkdirSync(configDir, { recursive: true }); writeFileSync( join(configDir, 'development.json'), JSON.stringify( { providers: { eod: { name: 'EOD Historical Data', apiKey: 'file-eod-key', baseUrl: 'https://file.eod.com/api', tier: 'free', enabled: false, priority: 1, }, yahoo: { name: 'Yahoo Finance', baseUrl: 'https://file.yahoo.com', enabled: true, priority: 2, }, }, }, null, 2 ) ); // Set environment variables that should override file config process.env.EOD_API_KEY = 'env-eod-key'; process.env.EOD_ENABLED = 'true'; process.env.EOD_PRIORITY = '10'; process.env.YAHOO_PRIORITY = '25'; const manager = new ConfigManager({ loaders: [new FileLoader(configDir, 'development'), new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); // EOD config should be merged (env overrides file) expect(config.providers?.eod?.name).toBe('EOD Historical Data'); // From file expect(config.providers?.eod?.apiKey).toBe('env-eod-key'); // From env expect(config.providers?.eod?.baseUrl).toBe('https://file.eod.com/api'); // From file expect(config.providers?.eod?.enabled).toBe(true); // From env (overrides file) expect(config.providers?.eod?.priority).toBe(10); // From env (overrides file) // Yahoo config should be merged expect(config.providers?.yahoo?.name).toBe('Yahoo Finance'); // From file expect(config.providers?.yahoo?.baseUrl).toBe('https://file.yahoo.com'); // From file expect(config.providers?.yahoo?.priority).toBe(25); // From env (overrides file) }); test('should handle invalid provider configurations', async () => { // Set invalid values process.env.EOD_TIER = 'invalid-tier'; // Should be one of ['free', 'fundamentals', 'all-in-one'] process.env.IB_MARKET_DATA_TYPE = 'invalid-type'; // Should be one of ['live', 'delayed', 'frozen'] process.env.IB_GATEWAY_PORT = 'not-a-number'; // Should be a number const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); // Should throw validation error await expect(manager.initialize(appConfigSchema)).rejects.toThrow(); }); test('should work with getProviderConfig helper function', async () => { // Set up multiple providers process.env.EOD_API_KEY = 'test-eod-key'; process.env.EOD_ENABLED = 'true'; process.env.WEBSHARE_API_KEY = 'test-webshare-key'; process.env.WEBSHARE_ENABLED = 'true'; const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); await manager.initialize(appConfigSchema); // Test getProviderConfig helper const eodConfig = getProviderConfig('eod'); expect(eodConfig).toBeDefined(); expect((eodConfig as any).apiKey).toBe('test-eod-key'); const webshareConfig = getProviderConfig('webshare'); expect(webshareConfig).toBeDefined(); expect((webshareConfig as any).apiKey).toBe('test-webshare-key'); // Test non-existent provider expect(() => getProviderConfig('nonexistent')).toThrow( 'Provider configuration not found: nonexistent' ); }); test('should handle boolean string parsing correctly', async () => { // Test various boolean representations process.env.EOD_ENABLED = 'TRUE'; process.env.YAHOO_ENABLED = 'False'; process.env.IB_ENABLED = '1'; process.env.QM_ENABLED = '0'; process.env.WEBSHARE_ENABLED = 'yes'; // Should be treated as string, not boolean const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.providers?.eod?.enabled).toBe(true); expect(config.providers?.yahoo?.enabled).toBe(false); expect(config.providers?.ib?.enabled).toBe(true); // 1 is parsed as number, not boolean expect(config.providers?.qm?.enabled).toBe(false); // 0 is parsed as number, not boolean // webshare.enabled should be the string 'yes', but schema validation might reject it }); test('should handle nested configuration correctly', async () => { // Test nested IB gateway configuration process.env.IB_GATEWAY_HOST = 'gateway.ib.com'; process.env.IB_GATEWAY_PORT = '7497'; process.env.IB_GATEWAY_CLIENT_ID = '999'; const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.providers?.ib?.gateway).toBeDefined(); expect(config.providers?.ib?.gateway.host).toBe('gateway.ib.com'); expect(config.providers?.ib?.gateway.port).toBe(7497); expect(config.providers?.ib?.gateway.clientId).toBe(999); }); test('should load provider configs from .env file', async () => { // Create .env file with provider configs writeFileSync( join(TEST_DIR, '.env'), `# Provider configurations EOD_API_KEY=env-file-eod-key EOD_ENABLED=true WEBSHARE_API_KEY=env-file-webshare-key IB_GATEWAY_HOST=env-file-ib-host IB_GATEWAY_PORT=7498 YAHOO_BASE_URL=https://env-file.yahoo.com ` ); const originalCwd = process.cwd(); try { process.chdir(TEST_DIR); const manager = new ConfigManager({ loaders: [new EnvLoader('')], }); const config = await manager.initialize(appConfigSchema); expect(config.providers?.eod?.apiKey).toBe('env-file-eod-key'); expect(config.providers?.eod?.enabled).toBe(true); expect(config.webshare?.apiKey).toBe('env-file-webshare-key'); expect(config.providers?.ib?.gateway.host).toBe('env-file-ib-host'); expect(config.providers?.ib?.gateway.port).toBe(7498); expect(config.providers?.yahoo?.baseUrl).toBe('https://env-file.yahoo.com'); } finally { process.chdir(originalCwd); } }); });