import { beforeEach, describe, expect, it } from 'bun:test'; import { z } from 'zod'; import { baseAppSchema, ConfigManager, createAppConfig } from '../src'; import { ConfigError, ConfigValidationError } from '../src/errors'; // 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 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 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 configManager = createAppConfig(schema, { loaders: [new MockLoader({ app: 'myapp', version: 1 })], }); const config = configManager.initialize(schema); expect(config).toEqual({ app: 'myapp', version: 1 }); }); }); describe('Environment Detection', () => { 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); } }); });