import { describe, test, expect, beforeEach } from 'bun:test'; import { z } from 'zod'; import { ConfigManager } from '../src/config-manager'; import { ConfigLoader } from '../src/types'; import { ConfigValidationError } from '../src/errors'; // Mock loader for testing class MockLoader implements ConfigLoader { priority = 0; constructor( private data: Record, public override priority: number = 0 ) {} async load(): Promise> { return this.data; } } // Test schema const testSchema = z.object({ app: z.object({ name: z.string(), version: z.string(), port: z.number().positive(), }), database: z.object({ host: z.string(), port: z.number(), }), environment: z.enum(['development', 'test', 'production']), }); type TestConfig = z.infer; describe('ConfigManager', () => { let manager: ConfigManager; beforeEach(() => { manager = new ConfigManager({ loaders: [ new MockLoader({ app: { name: 'test-app', version: '1.0.0', port: 3000, }, database: { host: 'localhost', port: 5432, }, }), ], environment: 'test', }); }); test('should initialize configuration', async () => { const config = await manager.initialize(testSchema); expect(config.app.name).toBe('test-app'); expect(config.app.version).toBe('1.0.0'); expect(config.environment).toBe('test'); }); test('should merge multiple loaders by priority', async () => { manager = new ConfigManager({ loaders: [ new MockLoader({ app: { name: 'base', port: 3000 } }, 0), new MockLoader({ app: { name: 'override', version: '2.0.0' } }, 10), new MockLoader({ database: { host: 'prod-db' } }, 5), ], environment: 'test', }); const config = await manager.initialize(); expect(config.app.name).toBe('override'); expect(config.app.version).toBe('2.0.0'); expect(config.app.port).toBe(3000); expect(config.database.host).toBe('prod-db'); }); test('should validate configuration with schema', async () => { manager = new ConfigManager({ loaders: [ new MockLoader({ app: { name: 'test-app', version: '1.0.0', port: 'invalid', // Should be number }, }), ], }); await expect(manager.initialize(testSchema)).rejects.toThrow(ConfigValidationError); }); test('should get configuration value by path', async () => { await manager.initialize(testSchema); expect(manager.getValue('app.name')).toBe('test-app'); expect(manager.getValue('database.port')).toBe(5432); }); test('should check if configuration path exists', async () => { await manager.initialize(testSchema); expect(manager.has('app.name')).toBe(true); expect(manager.has('app.nonexistent')).toBe(false); }); test('should update configuration at runtime', async () => { await manager.initialize(testSchema); manager.set({ app: { name: 'updated-app', }, }); const config = manager.get(); expect(config.app.name).toBe('updated-app'); expect(config.app.version).toBe('1.0.0'); // Should preserve other values }); test('should validate updates against schema', async () => { await manager.initialize(testSchema); expect(() => { manager.set({ app: { port: 'invalid' as any, }, }); }).toThrow(ConfigValidationError); }); test('should reset configuration', async () => { await manager.initialize(testSchema); manager.reset(); expect(() => manager.get()).toThrow('Configuration not initialized'); }); test('should create typed getter', async () => { await manager.initialize(testSchema); const appSchema = z.object({ app: z.object({ name: z.string(), version: z.string(), }), }); const getAppConfig = manager.createTypedGetter(appSchema); const appConfig = getAppConfig(); expect(appConfig.app.name).toBe('test-app'); }); test('should detect environment correctly', () => { const originalEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'production'; const prodManager = new ConfigManager({ loaders: [] }); expect(prodManager.getEnvironment()).toBe('production'); process.env.NODE_ENV = 'test'; const testManager = new ConfigManager({ loaders: [] }); expect(testManager.getEnvironment()).toBe('test'); process.env.NODE_ENV = originalEnv; }); test('should handle deep merge correctly', async () => { manager = new ConfigManager({ loaders: [ new MockLoader({ app: { settings: { feature1: true, feature2: false, nested: { value: 'base', }, }, }, }, 0), new MockLoader({ app: { settings: { feature2: true, feature3: true, nested: { value: 'override', extra: 'new', }, }, }, }, 10), ], }); const config = await manager.initialize(); expect(config.app.settings.feature1).toBe(true); expect(config.app.settings.feature2).toBe(true); expect(config.app.settings.feature3).toBe(true); expect(config.app.settings.nested.value).toBe('override'); expect(config.app.settings.nested.extra).toBe('new'); }); });