import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; import { writeFileSync, mkdirSync, rmSync } from 'fs'; import { join } from 'path'; import { EnvLoader } from '../src/loaders/env.loader'; import { FileLoader } from '../src/loaders/file.loader'; describe('EnvLoader', () => { const originalEnv = { ...process.env }; afterEach(() => { // Restore original environment process.env = { ...originalEnv }; }); test('should load environment variables with prefix', async () => { process.env.TEST_APP_NAME = 'env-app'; process.env.TEST_APP_VERSION = '1.0.0'; process.env.TEST_DATABASE_HOST = 'env-host'; process.env.TEST_DATABASE_PORT = '5432'; process.env.OTHER_VAR = 'should-not-load'; const loader = new EnvLoader('TEST_', { convertCase: false, nestedDelimiter: null }); const config = await loader.load(); expect(config.APP_NAME).toBe('env-app'); expect(config.APP_VERSION).toBe('1.0.0'); expect(config.DATABASE_HOST).toBe('env-host'); expect(config.DATABASE_PORT).toBe(5432); // Should be parsed as number expect(config.OTHER_VAR).toBeUndefined(); }); test('should convert snake_case to camelCase', async () => { process.env.TEST_DATABASE_CONNECTION_STRING = 'postgres://localhost'; process.env.TEST_API_KEY_SECRET = 'secret123'; const loader = new EnvLoader('TEST_', { convertCase: true }); const config = await loader.load(); expect(config.databaseConnectionString).toBe('postgres://localhost'); expect(config.apiKeySecret).toBe('secret123'); }); test('should parse JSON values', async () => { process.env.TEST_SETTINGS = '{"feature": true, "limit": 100}'; process.env.TEST_NUMBERS = '[1, 2, 3]'; const loader = new EnvLoader('TEST_', { parseJson: true }); const config = await loader.load(); expect(config.SETTINGS).toEqual({ feature: true, limit: 100 }); expect(config.NUMBERS).toEqual([1, 2, 3]); }); test('should parse boolean and number values', async () => { process.env.TEST_ENABLED = 'true'; process.env.TEST_DISABLED = 'false'; process.env.TEST_PORT = '3000'; process.env.TEST_RATIO = '0.75'; const loader = new EnvLoader('TEST_', { parseValues: true }); const config = await loader.load(); expect(config.ENABLED).toBe(true); expect(config.DISABLED).toBe(false); expect(config.PORT).toBe(3000); expect(config.RATIO).toBe(0.75); }); test('should handle nested object structure', async () => { process.env.TEST_APP__NAME = 'nested-app'; process.env.TEST_APP__SETTINGS__ENABLED = 'true'; process.env.TEST_DATABASE__HOST = 'localhost'; const loader = new EnvLoader('TEST_', { parseValues: true, nestedDelimiter: '__' }); const config = await loader.load(); expect(config.APP).toEqual({ NAME: 'nested-app', SETTINGS: { ENABLED: true } }); expect(config.DATABASE).toEqual({ HOST: 'localhost' }); }); }); describe('FileLoader', () => { const testDir = join(process.cwd(), 'test-config'); beforeEach(() => { mkdirSync(testDir, { recursive: true }); }); afterEach(() => { rmSync(testDir, { recursive: true, force: true }); }); test('should load JSON configuration file', async () => { const config = { app: { name: 'file-app', version: '1.0.0' }, database: { host: 'localhost', port: 5432 } }; writeFileSync( join(testDir, 'default.json'), JSON.stringify(config, null, 2) ); const loader = new FileLoader(testDir); const loaded = await loader.load(); expect(loaded).toEqual(config); }); test('should load environment-specific configuration', async () => { const defaultConfig = { app: { name: 'app', port: 3000 }, database: { host: 'localhost' } }; const prodConfig = { app: { port: 8080 }, database: { host: 'prod-db' } }; writeFileSync( join(testDir, 'default.json'), JSON.stringify(defaultConfig, null, 2) ); writeFileSync( join(testDir, 'production.json'), JSON.stringify(prodConfig, null, 2) ); const loader = new FileLoader(testDir, 'production'); const loaded = await loader.load(); expect(loaded).toEqual({ app: { name: 'app', port: 8080 }, database: { host: 'prod-db' } }); }); test('should handle missing configuration files gracefully', async () => { const loader = new FileLoader(testDir); const loaded = await loader.load(); expect(loaded).toEqual({}); }); test('should throw on invalid JSON', async () => { writeFileSync( join(testDir, 'default.json'), 'invalid json content' ); const loader = new FileLoader(testDir); await expect(loader.load()).rejects.toThrow(); }); test('should support custom configuration', async () => { const config = { custom: 'value' }; writeFileSync( join(testDir, 'custom.json'), JSON.stringify(config, null, 2) ); const loader = new FileLoader(testDir); const loaded = await loader.loadFile('custom.json'); expect(loaded).toEqual(config); }); });