This commit is contained in:
Boki 2025-06-22 17:55:51 -04:00
parent d858222af7
commit 7d9044ab29
202 changed files with 10755 additions and 10972 deletions

View file

@ -1,12 +1,12 @@
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import { mkdirSync, writeFileSync, rmSync, existsSync, chmodSync } from 'fs';
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import { ConfigManager } from '../src/config-manager';
import { FileLoader } from '../src/loaders/file.loader';
import { EnvLoader } from '../src/loaders/env.loader';
import { initializeConfig, initializeServiceConfig, resetConfig } from '../src/index';
import { appConfigSchema } from '../src/schemas';
import { ConfigError, ConfigValidationError } from '../src/errors';
import { initializeConfig, initializeServiceConfig, resetConfig } from '../src/index';
import { EnvLoader } from '../src/loaders/env.loader';
import { FileLoader } from '../src/loaders/file.loader';
import { appConfigSchema } from '../src/schemas';
const TEST_DIR = join(__dirname, 'edge-case-tests');
@ -17,9 +17,9 @@ describe('Edge Cases and Error Handling', () => {
beforeEach(() => {
originalEnv = { ...process.env };
originalCwd = process.cwd();
resetConfig();
if (existsSync(TEST_DIR)) {
rmSync(TEST_DIR, { recursive: true, force: true });
}
@ -30,7 +30,7 @@ describe('Edge Cases and Error Handling', () => {
process.env = originalEnv;
process.chdir(originalCwd);
resetConfig();
if (existsSync(TEST_DIR)) {
rmSync(TEST_DIR, { recursive: true, force: true });
}
@ -39,7 +39,7 @@ describe('Edge Cases and Error Handling', () => {
test('should handle missing .env files gracefully', async () => {
// No .env file exists
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
// Should not throw even without .env file
@ -50,15 +50,12 @@ describe('Edge Cases and Error Handling', () => {
test('should handle corrupted JSON config files', async () => {
const configDir = join(TEST_DIR, 'config');
mkdirSync(configDir, { recursive: true });
// Create corrupted JSON file
writeFileSync(
join(configDir, 'development.json'),
'{ "app": { "name": "test", invalid json }'
);
writeFileSync(join(configDir, 'development.json'), '{ "app": { "name": "test", invalid json }');
const manager = new ConfigManager({
loaders: [new FileLoader(configDir, 'development')]
loaders: [new FileLoader(configDir, 'development')],
});
// Should throw error for invalid JSON
@ -67,9 +64,9 @@ describe('Edge Cases and Error Handling', () => {
test('should handle missing config directories', async () => {
const nonExistentDir = join(TEST_DIR, 'nonexistent');
const manager = new ConfigManager({
loaders: [new FileLoader(nonExistentDir, 'development')]
loaders: [new FileLoader(nonExistentDir, 'development')],
});
// Should not throw, should return empty config
@ -80,16 +77,16 @@ describe('Edge Cases and Error Handling', () => {
test('should handle permission denied on config files', async () => {
const configDir = join(TEST_DIR, 'config');
mkdirSync(configDir, { recursive: true });
const configFile = join(configDir, 'development.json');
writeFileSync(configFile, JSON.stringify({ app: { name: 'test' } }));
// Make file unreadable (this might not work on all systems)
try {
chmodSync(configFile, 0o000);
const manager = new ConfigManager({
loaders: [new FileLoader(configDir, 'development')]
loaders: [new FileLoader(configDir, 'development')],
});
// Should handle permission error gracefully
@ -109,26 +106,23 @@ describe('Edge Cases and Error Handling', () => {
// This tests deep merge with potential circular references
const configDir = join(TEST_DIR, 'config');
mkdirSync(configDir, { recursive: true });
writeFileSync(
join(configDir, 'development.json'),
JSON.stringify({
app: {
name: 'test',
settings: {
ref: 'settings'
}
}
ref: 'settings',
},
},
})
);
process.env.APP_SETTINGS_NESTED_VALUE = 'deep-value';
const manager = new ConfigManager({
loaders: [
new FileLoader(configDir, 'development'),
new EnvLoader('')
]
loaders: [new FileLoader(configDir, 'development'), new EnvLoader('')],
});
const config = await manager.initialize(appConfigSchema);
@ -138,13 +132,13 @@ describe('Edge Cases and Error Handling', () => {
test('should handle extremely deep nesting in environment variables', async () => {
// Test very deep nesting
process.env.LEVEL1_LEVEL2_LEVEL3_LEVEL4_LEVEL5_VALUE = 'deep-value';
const manager = new ConfigManager({
loaders: [new EnvLoader('', { nestedDelimiter: '_' })]
loaders: [new EnvLoader('', { nestedDelimiter: '_' })],
});
const config = await manager.initialize();
// Should create nested structure
expect((config as any).level1?.level2?.level3?.level4?.level5?.value).toBe('deep-value');
});
@ -152,15 +146,15 @@ describe('Edge Cases and Error Handling', () => {
test('should handle conflicting data types in config merging', async () => {
const configDir = join(TEST_DIR, 'config');
mkdirSync(configDir, { recursive: true });
// File config has object
writeFileSync(
join(configDir, 'development.json'),
JSON.stringify({
database: {
host: 'localhost',
port: 5432
}
port: 5432,
},
})
);
@ -168,14 +162,11 @@ describe('Edge Cases and Error Handling', () => {
process.env.DATABASE = 'simple-string';
const manager = new ConfigManager({
loaders: [
new FileLoader(configDir, 'development'),
new EnvLoader('')
]
loaders: [new FileLoader(configDir, 'development'), new EnvLoader('')],
});
const config = await manager.initialize(appConfigSchema);
// Environment variable should win
expect(config.database).toBe('simple-string');
});
@ -184,15 +175,15 @@ describe('Edge Cases and Error Handling', () => {
// Create multiple config setups in different directories
const dir1 = join(TEST_DIR, 'dir1');
const dir2 = join(TEST_DIR, 'dir2');
mkdirSync(join(dir1, 'config'), { recursive: true });
mkdirSync(join(dir2, 'config'), { recursive: true });
writeFileSync(
join(dir1, 'config', 'development.json'),
JSON.stringify({ app: { name: 'dir1-app' } })
);
writeFileSync(
join(dir2, 'config', 'development.json'),
JSON.stringify({ app: { name: 'dir2-app' } })
@ -229,13 +220,13 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
);
process.chdir(TEST_DIR);
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
const config = await manager.initialize();
// Should handle valid entries
expect(process.env.VALID_KEY).toBe('valid_value');
expect(process.env.KEY_WITH_QUOTES).toBe('quoted value');
@ -245,12 +236,12 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
test('should handle empty config files', async () => {
const configDir = join(TEST_DIR, 'config');
mkdirSync(configDir, { recursive: true });
// Create empty JSON file
writeFileSync(join(configDir, 'development.json'), '{}');
const manager = new ConfigManager({
loaders: [new FileLoader(configDir, 'development')]
loaders: [new FileLoader(configDir, 'development')],
});
const config = await manager.initialize(appConfigSchema);
@ -260,7 +251,7 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
test('should handle config initialization without schema', async () => {
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
// Initialize without schema
@ -271,7 +262,7 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
test('should handle accessing config before initialization', () => {
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
// Should throw error when accessing uninitialized config
@ -282,15 +273,15 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
test('should handle invalid config paths in getValue', async () => {
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
const config = await manager.initialize(appConfigSchema);
// Should throw for invalid paths
expect(() => manager.getValue('nonexistent.path')).toThrow('Configuration key not found');
expect(() => manager.getValue('app.nonexistent')).toThrow('Configuration key not found');
// Should work for valid paths
expect(() => manager.getValue('environment')).not.toThrow();
});
@ -301,11 +292,11 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
process.env.EMPTY_VALUE = '';
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
const config = await manager.initialize();
expect((config as any).null_value).toBe(null);
expect((config as any).undefined_value).toBe(undefined);
expect((config as any).empty_value).toBe('');
@ -318,7 +309,7 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
process.env.SERVICE_PORT = 'not-a-number'; // This should cause validation to fail
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
await expect(manager.initialize(appConfigSchema)).rejects.toThrow(ConfigValidationError);
@ -326,7 +317,7 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
test('should handle config updates with invalid schema', async () => {
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
await manager.initialize(appConfigSchema);
@ -335,8 +326,8 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
expect(() => {
manager.set({
service: {
port: 'invalid-port' as any
}
port: 'invalid-port' as any,
},
});
}).toThrow(ConfigValidationError);
});
@ -344,7 +335,7 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
test('should handle loader priority conflicts', async () => {
const configDir = join(TEST_DIR, 'config');
mkdirSync(configDir, { recursive: true });
writeFileSync(
join(configDir, 'development.json'),
JSON.stringify({ app: { name: 'file-config' } })
@ -356,12 +347,12 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
const manager = new ConfigManager({
loaders: [
new FileLoader(configDir, 'development'), // priority 50
new EnvLoader('') // priority 100
]
new EnvLoader(''), // priority 100
],
});
const config = await manager.initialize(appConfigSchema);
// Environment should win due to higher priority
expect(config.app.name).toBe('env-config');
});
@ -369,16 +360,16 @@ JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
test('should handle readonly environment variables', async () => {
// Some system environment variables might be readonly
const originalPath = process.env.PATH;
// This should not cause the loader to fail
const manager = new ConfigManager({
loaders: [new EnvLoader('')]
loaders: [new EnvLoader('')],
});
const config = await manager.initialize();
expect(config).toBeDefined();
// PATH should not be modified
expect(process.env.PATH).toBe(originalPath);
});
});
});