import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test'; import { z } from 'zod'; import { baseAppSchema, ConfigError, ConfigManager, ConfigValidationError, createAppConfig, getConfig, getDatabaseConfig, getLogConfig, getQueueConfig, getServiceConfig, initializeAppConfig, initializeServiceConfig, isDevelopment, isProduction, isTest, resetConfig, } from '../src'; // Mock loader for testing class MockLoader { constructor( private data: Record, public priority: number = 0 ) {} load(): Record { return this.data; } } describe('ConfigManager', () => { let manager: ConfigManager; beforeEach(() => { manager = new ConfigManager(); }); it('should initialize with default loaders', () => { expect(manager).toBeDefined(); }); it('should detect environment', () => { const env = manager.getEnvironment(); expect(['development', 'test', 'production']).toContain(env); }); it('should throw when getting config before initialization', () => { expect(() => manager.get()).toThrow(ConfigError); }); it('should initialize config with schema', () => { const schema = z.object({ name: z.string(), port: z.number(), }); const mockManager = new ConfigManager({ loaders: [new MockLoader({ name: 'test', port: 3000 })], }); const config = mockManager.initialize(schema); expect(config).toEqual({ name: 'test', port: 3000 }); }); it('should merge configs from multiple loaders', () => { const mockManager = new ConfigManager({ loaders: [ new MockLoader({ name: 'test', port: 3000 }, 1), new MockLoader({ port: 4000, debug: true }, 2), ], }); const config = mockManager.initialize(); expect(config).toEqual({ name: 'test', port: 4000, debug: true, environment: 'test' }); }); it('should deep merge nested objects', () => { const mockManager = new ConfigManager({ loaders: [ new MockLoader({ db: { host: 'localhost', port: 5432 } }, 1), new MockLoader({ db: { port: 5433, user: 'admin' } }, 2), ], }); const config = mockManager.initialize(); expect(config).toEqual({ db: { host: 'localhost', port: 5433, user: 'admin' }, environment: 'test', }); }); it('should get value by path', () => { const mockManager = new ConfigManager({ loaders: [new MockLoader({ db: { host: 'localhost', port: 5432 } })], }); mockManager.initialize(); expect(mockManager.getValue('db.host')).toBe('localhost'); expect(mockManager.getValue('db.port')).toBe(5432); }); it('should throw for non-existent path', () => { const mockManager = new ConfigManager({ loaders: [new MockLoader({ db: { host: 'localhost' } })], }); mockManager.initialize(); expect(() => mockManager.getValue('db.password')).toThrow(ConfigError); }); it('should check if path exists', () => { const mockManager = new ConfigManager({ loaders: [new MockLoader({ db: { host: 'localhost' } })], }); mockManager.initialize(); expect(mockManager.has('db.host')).toBe(true); expect(mockManager.has('db.password')).toBe(false); }); it('should update config at runtime', () => { const mockManager = new ConfigManager({ loaders: [new MockLoader({ name: 'test', port: 3000 })], }); mockManager.initialize(); mockManager.set({ port: 4000 }); expect(mockManager.get()).toEqual({ name: 'test', port: 4000, environment: 'test' }); }); it('should validate config update with schema', () => { const schema = z.object({ name: z.string(), port: z.number(), }); const mockManager = new ConfigManager({ loaders: [new MockLoader({ name: 'test', port: 3000 })], }); mockManager.initialize(schema); expect(() => mockManager.set({ port: 'invalid' as any })).toThrow(ConfigValidationError); }); it('should reset config', () => { const mockManager = new ConfigManager({ loaders: [new MockLoader({ name: 'test' })], }); mockManager.initialize(); expect(mockManager.get()).toEqual({ name: 'test', environment: 'test' }); mockManager.reset(); expect(() => mockManager.get()).toThrow(ConfigError); }); it('should validate against schema', () => { const schema = z.object({ name: z.string(), port: z.number(), }); const mockManager = new ConfigManager({ loaders: [new MockLoader({ name: 'test', port: 3000 })], }); mockManager.initialize(); const validated = mockManager.validate(schema); expect(validated).toEqual({ name: 'test', port: 3000 }); }); it('should create typed getter', () => { const schema = z.object({ name: z.string(), port: z.number(), }); const mockManager = new ConfigManager({ loaders: [new MockLoader({ name: 'test', port: 3000 })], }); mockManager.initialize(); const getTypedConfig = mockManager.createTypedGetter(schema); const config = getTypedConfig(); expect(config).toEqual({ name: 'test', port: 3000 }); }); it('should add environment if not present', () => { const mockManager = new ConfigManager({ environment: 'test', loaders: [new MockLoader({ name: 'test' })], }); const config = mockManager.initialize(); expect(config).toEqual({ name: 'test', environment: 'test' }); }); }); describe('Config Service Functions', () => { beforeEach(() => { resetConfig(); }); it('should throw when getting config before initialization', () => { expect(() => getConfig()).toThrow(ConfigError); }); it('should validate config with schema', () => { // Test that a valid config passes schema validation const mockConfig = { name: 'test-app', version: '1.0.0', environment: 'test' as const, service: { name: 'test-service', baseUrl: 'http://localhost:3000', port: 3000, }, database: { mongodb: { uri: 'mongodb://localhost', database: 'test-db', }, postgres: { host: 'localhost', port: 5432, database: 'test-db', user: 'test-user', password: 'test-pass', }, questdb: { host: 'localhost', httpPort: 9000, }, }, log: { level: 'info' as const, pretty: true, }, queue: { redis: { host: 'localhost', port: 6379 }, }, }; const manager = new ConfigManager({ loaders: [new MockLoader(mockConfig)], }); // Should not throw when initializing with valid config expect(() => manager.initialize(baseAppSchema)).not.toThrow(); // Verify key properties exist const config = manager.get(); expect(config.name).toBe('test-app'); expect(config.version).toBe('1.0.0'); expect(config.environment).toBe('test'); expect(config.service.name).toBe('test-service'); expect(config.database.mongodb.uri).toBe('mongodb://localhost'); }); }); describe('Config Builders', () => { it('should create app config with schema', () => { const schema = z.object({ app: z.string(), version: z.number(), }); const config = createAppConfig(schema, { loaders: [new MockLoader({ app: 'myapp', version: 1 })], }); expect(config).toBeDefined(); }); it('should initialize app config in one step', () => { const schema = z.object({ app: z.string(), version: z.number(), }); const config = initializeAppConfig(schema, { loaders: [new MockLoader({ app: 'myapp', version: 1 })], }); expect(config).toEqual({ app: 'myapp', version: 1 }); }); }); describe('Environment Helpers', () => { beforeEach(() => { resetConfig(); }); afterEach(() => { resetConfig(); }); it('should detect environments correctly in ConfigManager', () => { // Test with different environments using mock configs const envConfigs = [{ env: 'development' }, { env: 'production' }, { env: 'test' }]; for (const { env } of envConfigs) { const mockConfig = { name: 'test-app', version: '1.0.0', environment: env as 'development' | 'production' | 'test', service: { name: 'test', port: 3000, }, database: { mongodb: { uri: 'mongodb://localhost', database: 'test-db', }, postgres: { host: 'localhost', port: 5432, database: 'test-db', user: 'test-user', password: 'test-pass', }, questdb: { host: 'localhost', httpPort: 9000, }, }, log: { level: 'info' as const, pretty: true, }, queue: { redis: { host: 'localhost', port: 6379 }, }, }; const manager = new ConfigManager({ loaders: [new MockLoader(mockConfig)], environment: env as any, }); manager.initialize(baseAppSchema); // Test the manager's environment detection expect(manager.getEnvironment()).toBe(env); expect(manager.get().environment).toBe(env); } }); });