import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; import { join } from 'path'; import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs'; import { initializeConfig, initializeServiceConfig, getConfig, getDatabaseConfig, getServiceConfig, getLoggingConfig, getProviderConfig, isDevelopment, isProduction, isTest, resetConfig } from '../src/index'; const TEST_DIR = join(__dirname, 'real-usage-tests'); describe('Real Usage Scenarios', () => { let originalEnv: NodeJS.ProcessEnv; let originalCwd: string; beforeEach(() => { originalEnv = { ...process.env }; originalCwd = process.cwd(); resetConfig(); if (existsSync(TEST_DIR)) { rmSync(TEST_DIR, { recursive: true, force: true }); } setupRealUsageScenarios(); }); afterEach(() => { process.env = originalEnv; process.chdir(originalCwd); resetConfig(); if (existsSync(TEST_DIR)) { rmSync(TEST_DIR, { recursive: true, force: true }); } }); test('should work like real data-service usage', async () => { const dataServiceDir = join(TEST_DIR, 'apps', 'data-service'); process.chdir(dataServiceDir); // Simulate how data-service would initialize config const config = await initializeServiceConfig(); // Test typical data-service config access patterns expect(config.app.name).toBe('data-service'); expect(config.service.port).toBe(3001); // Test database config access const dbConfig = getDatabaseConfig(); expect(dbConfig.postgres.host).toBe('localhost'); expect(dbConfig.postgres.port).toBe(5432); expect(dbConfig.questdb.host).toBe('localhost'); // Test provider access const yahooConfig = getProviderConfig('yahoo'); expect(yahooConfig).toBeDefined(); expect((yahooConfig as any).enabled).toBe(true); // Test environment helpers expect(isDevelopment()).toBe(true); expect(isProduction()).toBe(false); }); test('should work like real web-api usage', async () => { const webApiDir = join(TEST_DIR, 'apps', 'web-api'); process.chdir(webApiDir); const config = await initializeServiceConfig(); expect(config.app.name).toBe('web-api'); expect(config.service.port).toBe(4000); // Web API should have access to all the same configs const serviceConfig = getServiceConfig(); expect(serviceConfig.name).toBe('web-api'); const loggingConfig = getLoggingConfig(); expect(loggingConfig.level).toBe('info'); }); test('should work like real shared library usage', async () => { const cacheLibDir = join(TEST_DIR, 'libs', 'cache'); process.chdir(cacheLibDir); const config = await initializeServiceConfig(); // Libraries should inherit from root config expect(config.app.name).toBe('cache-lib'); expect(config.app.version).toBe('1.0.0'); // From root // Should have access to cache config const dbConfig = getDatabaseConfig(); expect(dbConfig.dragonfly).toBeDefined(); expect(dbConfig.dragonfly.host).toBe('localhost'); expect(dbConfig.dragonfly.port).toBe(6379); }); test('should handle production environment correctly', async () => { process.env.NODE_ENV = 'production'; const dataServiceDir = join(TEST_DIR, 'apps', 'data-service'); process.chdir(dataServiceDir); resetConfig(); const config = await initializeServiceConfig(); expect(config.environment).toBe('production'); expect(config.logging.level).toBe('warn'); // Production should use different log level expect(isProduction()).toBe(true); expect(isDevelopment()).toBe(false); }); test('should handle test environment correctly', async () => { process.env.NODE_ENV = 'test'; const dataServiceDir = join(TEST_DIR, 'apps', 'data-service'); process.chdir(dataServiceDir); resetConfig(); const config = await initializeServiceConfig(); expect(config.environment).toBe('test'); expect(config.logging.level).toBe('debug'); // Test should use debug level expect(isTest()).toBe(true); expect(isDevelopment()).toBe(false); }); test('should work with environment variable overrides in production', async () => { process.env.NODE_ENV = 'production'; process.env.DATABASE_POSTGRES_HOST = 'prod-db.example.com'; process.env.DATABASE_POSTGRES_PORT = '5433'; process.env.EOD_API_KEY = 'prod-eod-key'; process.env.SERVICE_PORT = '8080'; const dataServiceDir = join(TEST_ROOT, 'apps', 'data-service'); process.chdir(dataServiceDir); resetConfig(); const config = await initializeServiceConfig(); // Environment variables should override file configs const dbConfig = getDatabaseConfig(); expect(dbConfig.postgres.host).toBe('prod-db.example.com'); expect(dbConfig.postgres.port).toBe(5433); const serviceConfig = getServiceConfig(); expect(serviceConfig.port).toBe(8080); const eodConfig = getProviderConfig('eod'); expect((eodConfig as any).apiKey).toBe('prod-eod-key'); }); test('should handle missing provider configurations gracefully', async () => { const dataServiceDir = join(TEST_DIR, 'apps', 'data-service'); process.chdir(dataServiceDir); const config = await initializeServiceConfig(); // Should throw for non-existent providers expect(() => getProviderConfig('nonexistent')).toThrow('Provider configuration not found: nonexistent'); // Should work for providers that exist but might not be configured // (they should have defaults from schema) const yahooConfig = getProviderConfig('yahoo'); expect(yahooConfig).toBeDefined(); }); test('should support dynamic config access patterns', async () => { const dataServiceDir = join(TEST_DIR, 'apps', 'data-service'); process.chdir(dataServiceDir); const config = await initializeServiceConfig(); // Test various access patterns used in real applications const configManager = (await import('../src/index')).getConfigManager(); // Direct path access expect(configManager.getValue('app.name')).toBe('data-service'); expect(configManager.getValue('service.port')).toBe(3001); // Check if paths exist expect(configManager.has('app.name')).toBe(true); expect(configManager.has('nonexistent.path')).toBe(false); // Typed access const port = configManager.getValue('service.port'); expect(typeof port).toBe('number'); expect(port).toBe(3001); }); test('should handle config updates at runtime', async () => { const dataServiceDir = join(TEST_DIR, 'apps', 'data-service'); process.chdir(dataServiceDir); await initializeServiceConfig(); const configManager = (await import('../src/index')).getConfigManager(); // Update config at runtime (useful for testing) configManager.set({ service: { port: 9999 } }); const updatedConfig = getConfig(); expect(updatedConfig.service.port).toBe(9999); // Other values should be preserved expect(updatedConfig.app.name).toBe('data-service'); }); test('should work across multiple service initializations', async () => { // Simulate multiple services in the same process (like tests) // First service const dataServiceDir = join(TEST_DIR, 'apps', 'data-service'); process.chdir(dataServiceDir); let config = await initializeServiceConfig(); expect(config.app.name).toBe('data-service'); // Reset and switch to another service resetConfig(); const webApiDir = join(TEST_DIR, 'apps', 'web-api'); process.chdir(webApiDir); config = await initializeServiceConfig(); expect(config.app.name).toBe('web-api'); // Each service should get its own config expect(config.service.port).toBe(4000); // web-api port }); }); const TEST_ROOT = TEST_DIR; function setupRealUsageScenarios() { const scenarios = { root: TEST_ROOT, dataService: join(TEST_ROOT, 'apps', 'data-service'), webApi: join(TEST_ROOT, 'apps', 'web-api'), cacheLib: join(TEST_ROOT, 'libs', 'cache'), }; // Create directory structure Object.values(scenarios).forEach(dir => { mkdirSync(join(dir, 'config'), { recursive: true }); }); // Root config (monorepo/config/*) const rootConfigs = { development: { app: { name: 'stock-bot-monorepo', version: '1.0.0' }, database: { postgres: { host: 'localhost', port: 5432, database: 'trading_bot', username: 'trading_user', password: 'trading_pass_dev' }, questdb: { host: 'localhost', port: 9009, database: 'questdb' }, mongodb: { host: 'localhost', port: 27017, database: 'stock' }, dragonfly: { host: 'localhost', port: 6379 } }, logging: { level: 'info', format: 'json' }, providers: { yahoo: { name: 'Yahoo Finance', enabled: true, priority: 1, baseUrl: 'https://query1.finance.yahoo.com' }, eod: { name: 'EOD Historical Data', enabled: false, priority: 2, apiKey: 'demo-api-key', baseUrl: 'https://eodhistoricaldata.com/api' } } }, production: { logging: { level: 'warn' }, database: { postgres: { host: 'prod-postgres.internal', port: 5432 } } }, test: { logging: { level: 'debug' }, database: { postgres: { database: 'trading_bot_test' } } } }; Object.entries(rootConfigs).forEach(([env, config]) => { writeFileSync( join(scenarios.root, 'config', `${env}.json`), JSON.stringify(config, null, 2) ); }); // Data service config writeFileSync( join(scenarios.dataService, 'config', 'development.json'), JSON.stringify({ app: { name: 'data-service' }, service: { name: 'data-service', port: 3001, workers: 2 } }, null, 2) ); // Web API config writeFileSync( join(scenarios.webApi, 'config', 'development.json'), JSON.stringify({ app: { name: 'web-api' }, service: { name: 'web-api', port: 4000, cors: { origin: ['http://localhost:3000', 'http://localhost:4200'] } } }, null, 2) ); // Cache lib config writeFileSync( join(scenarios.cacheLib, 'config', 'development.json'), JSON.stringify({ app: { name: 'cache-lib' }, service: { name: 'cache-lib' } }, null, 2) ); // Root .env file writeFileSync( join(scenarios.root, '.env'), `NODE_ENV=development DEBUG=true # Provider API keys EOD_API_KEY=demo-key WEBSHARE_API_KEY=demo-webshare-key ` ); // Service-specific .env files writeFileSync( join(scenarios.dataService, '.env'), `SERVICE_DEBUG=true DATA_SERVICE_RATE_LIMIT=1000 ` ); }