moved folders around
This commit is contained in:
parent
4f89affc2b
commit
36cb84b343
202 changed files with 1160 additions and 660 deletions
384
libs/core/config/test/edge-cases.test.ts
Normal file
384
libs/core/config/test/edge-cases.test.ts
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { join } from 'path';
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync, chmodSync } from 'fs';
|
||||
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';
|
||||
|
||||
const TEST_DIR = join(__dirname, 'edge-case-tests');
|
||||
|
||||
describe('Edge Cases and Error Handling', () => {
|
||||
let originalEnv: NodeJS.ProcessEnv;
|
||||
let originalCwd: string;
|
||||
|
||||
beforeEach(() => {
|
||||
originalEnv = { ...process.env };
|
||||
originalCwd = process.cwd();
|
||||
|
||||
resetConfig();
|
||||
|
||||
if (existsSync(TEST_DIR)) {
|
||||
rmSync(TEST_DIR, { recursive: true, force: true });
|
||||
}
|
||||
mkdirSync(TEST_DIR, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
process.chdir(originalCwd);
|
||||
resetConfig();
|
||||
|
||||
if (existsSync(TEST_DIR)) {
|
||||
rmSync(TEST_DIR, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle missing .env files gracefully', async () => {
|
||||
// No .env file exists
|
||||
const manager = new ConfigManager({
|
||||
loaders: [new EnvLoader('')]
|
||||
});
|
||||
|
||||
// Should not throw even without .env file
|
||||
const config = await manager.initialize(appConfigSchema);
|
||||
expect(config).toBeDefined();
|
||||
});
|
||||
|
||||
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 }'
|
||||
);
|
||||
|
||||
const manager = new ConfigManager({
|
||||
loaders: [new FileLoader(configDir, 'development')]
|
||||
});
|
||||
|
||||
// Should throw error for invalid JSON
|
||||
await expect(manager.initialize(appConfigSchema)).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should handle missing config directories', async () => {
|
||||
const nonExistentDir = join(TEST_DIR, 'nonexistent');
|
||||
|
||||
const manager = new ConfigManager({
|
||||
loaders: [new FileLoader(nonExistentDir, 'development')]
|
||||
});
|
||||
|
||||
// Should not throw, should return empty config
|
||||
const config = await manager.initialize();
|
||||
expect(config).toBeDefined();
|
||||
});
|
||||
|
||||
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')]
|
||||
});
|
||||
|
||||
// Should handle permission error gracefully
|
||||
const config = await manager.initialize();
|
||||
expect(config).toBeDefined();
|
||||
} finally {
|
||||
// Restore permissions for cleanup
|
||||
try {
|
||||
chmodSync(configFile, 0o644);
|
||||
} catch {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle circular references in config merging', async () => {
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
process.env.APP_SETTINGS_NESTED_VALUE = 'deep-value';
|
||||
|
||||
const manager = new ConfigManager({
|
||||
loaders: [
|
||||
new FileLoader(configDir, 'development'),
|
||||
new EnvLoader('')
|
||||
]
|
||||
});
|
||||
|
||||
const config = await manager.initialize(appConfigSchema);
|
||||
expect(config.app.name).toBe('test');
|
||||
});
|
||||
|
||||
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: '_' })]
|
||||
});
|
||||
|
||||
const config = await manager.initialize();
|
||||
|
||||
// Should create nested structure
|
||||
expect((config as any).level1?.level2?.level3?.level4?.level5?.value).toBe('deep-value');
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Environment variable tries to override with string
|
||||
process.env.DATABASE = 'simple-string';
|
||||
|
||||
const manager = new ConfigManager({
|
||||
loaders: [
|
||||
new FileLoader(configDir, 'development'),
|
||||
new EnvLoader('')
|
||||
]
|
||||
});
|
||||
|
||||
const config = await manager.initialize(appConfigSchema);
|
||||
|
||||
// Environment variable should win
|
||||
expect(config.database).toBe('simple-string');
|
||||
});
|
||||
|
||||
test('should handle different working directories', async () => {
|
||||
// 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' } })
|
||||
);
|
||||
|
||||
// Test from dir1
|
||||
process.chdir(dir1);
|
||||
resetConfig();
|
||||
let config = await initializeConfig();
|
||||
expect(config.app.name).toBe('dir1-app');
|
||||
|
||||
// Test from dir2
|
||||
process.chdir(dir2);
|
||||
resetConfig();
|
||||
config = await initializeConfig();
|
||||
expect(config.app.name).toBe('dir2-app');
|
||||
});
|
||||
|
||||
test('should handle malformed .env files', async () => {
|
||||
// Create malformed .env file
|
||||
writeFileSync(
|
||||
join(TEST_DIR, '.env'),
|
||||
`# Good line
|
||||
VALID_KEY=valid_value
|
||||
# Malformed lines
|
||||
MISSING_VALUE=
|
||||
=MISSING_KEY
|
||||
SPACES IN KEY=value
|
||||
KEY_WITH_QUOTES="quoted value"
|
||||
KEY_WITH_SINGLE_QUOTES='single quoted'
|
||||
# Complex value
|
||||
JSON_VALUE={"key": "value", "nested": {"array": [1, 2, 3]}}
|
||||
`
|
||||
);
|
||||
|
||||
process.chdir(TEST_DIR);
|
||||
|
||||
const manager = new ConfigManager({
|
||||
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');
|
||||
expect(process.env.KEY_WITH_SINGLE_QUOTES).toBe('single quoted');
|
||||
});
|
||||
|
||||
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')]
|
||||
});
|
||||
|
||||
const config = await manager.initialize(appConfigSchema);
|
||||
expect(config).toBeDefined();
|
||||
expect(config.environment).toBe('development'); // Should have default
|
||||
});
|
||||
|
||||
test('should handle config initialization without schema', async () => {
|
||||
const manager = new ConfigManager({
|
||||
loaders: [new EnvLoader('')]
|
||||
});
|
||||
|
||||
// Initialize without schema
|
||||
const config = await manager.initialize();
|
||||
expect(config).toBeDefined();
|
||||
expect(typeof config).toBe('object');
|
||||
});
|
||||
|
||||
test('should handle accessing config before initialization', () => {
|
||||
const manager = new ConfigManager({
|
||||
loaders: [new EnvLoader('')]
|
||||
});
|
||||
|
||||
// Should throw error when accessing uninitialized config
|
||||
expect(() => manager.get()).toThrow('Configuration not initialized');
|
||||
expect(() => manager.getValue('some.path')).toThrow('Configuration not initialized');
|
||||
expect(() => manager.has('some.path')).toThrow('Configuration not initialized');
|
||||
});
|
||||
|
||||
test('should handle invalid config paths in getValue', async () => {
|
||||
const manager = new ConfigManager({
|
||||
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();
|
||||
});
|
||||
|
||||
test('should handle null and undefined values in config', async () => {
|
||||
process.env.NULL_VALUE = 'null';
|
||||
process.env.UNDEFINED_VALUE = 'undefined';
|
||||
process.env.EMPTY_VALUE = '';
|
||||
|
||||
const manager = new ConfigManager({
|
||||
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('');
|
||||
});
|
||||
|
||||
test('should handle schema validation failures', async () => {
|
||||
// Set up config that will fail schema validation
|
||||
process.env.APP_NAME = 'valid-name';
|
||||
process.env.APP_VERSION = 'valid-version';
|
||||
process.env.SERVICE_PORT = 'not-a-number'; // This should cause validation to fail
|
||||
|
||||
const manager = new ConfigManager({
|
||||
loaders: [new EnvLoader('')]
|
||||
});
|
||||
|
||||
await expect(manager.initialize(appConfigSchema)).rejects.toThrow(ConfigValidationError);
|
||||
});
|
||||
|
||||
test('should handle config updates with invalid schema', async () => {
|
||||
const manager = new ConfigManager({
|
||||
loaders: [new EnvLoader('')]
|
||||
});
|
||||
|
||||
await manager.initialize(appConfigSchema);
|
||||
|
||||
// Try to update with invalid data
|
||||
expect(() => {
|
||||
manager.set({
|
||||
service: {
|
||||
port: 'invalid-port' as any
|
||||
}
|
||||
});
|
||||
}).toThrow(ConfigValidationError);
|
||||
});
|
||||
|
||||
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' } })
|
||||
);
|
||||
|
||||
process.env.APP_NAME = 'env-config';
|
||||
|
||||
// Create loaders with different priorities
|
||||
const manager = new ConfigManager({
|
||||
loaders: [
|
||||
new FileLoader(configDir, 'development'), // priority 50
|
||||
new EnvLoader('') // priority 100
|
||||
]
|
||||
});
|
||||
|
||||
const config = await manager.initialize(appConfigSchema);
|
||||
|
||||
// Environment should win due to higher priority
|
||||
expect(config.app.name).toBe('env-config');
|
||||
});
|
||||
|
||||
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('')]
|
||||
});
|
||||
|
||||
const config = await manager.initialize();
|
||||
expect(config).toBeDefined();
|
||||
|
||||
// PATH should not be modified
|
||||
expect(process.env.PATH).toBe(originalPath);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue