@snow-tzu/type-config
Version:
Core configuration management system with Spring Boot-like features
229 lines (190 loc) • 6.22 kB
text/typescript
import 'reflect-metadata';
import { ConfigManager } from '../src/config-manager';
import {
ConfigurationProperties,
ConfigProperty,
DefaultValue,
Required,
Validate,
} from '../src/decorators';
import { IsNumber, Min, Max, IsString } from 'class-validator';
describe('ConfigManager - Nested Class Integration', () => {
let manager: ConfigManager;
beforeEach(() => {
// Create a manager with in-memory config
manager = new ConfigManager({
validateOnBind: true,
});
});
afterEach(async () => {
await manager.dispose();
});
it('should bind nested configuration classes through bind() method', async () => {
// Define nested class
class CircuitBreakerOptions {
timeout!: number;
errorThresholdPercentage!: number;
volumeThreshold!: number;
}
// Define parent class
class SampleClientConfig {
baseURL!: string;
circuitBreaker!: CircuitBreakerOptions;
}
// Set up config manually
(manager as any).config = {
clients: {
sample: {
baseUrl: 'https://api.example.com',
circuitBreaker: {
volumeThreshold: 10,
timeout: 5000,
},
},
},
};
const config = manager.bind(SampleClientConfig);
expect(config).toBeInstanceOf(SampleClientConfig);
expect(config.baseURL).toBe('https://api.example.com');
expect(config.circuitBreaker).toBeInstanceOf(CircuitBreakerOptions);
expect(config.circuitBreaker.timeout).toBe(5000);
expect(config.circuitBreaker.errorThresholdPercentage).toBe(50); // Default
expect(config.circuitBreaker.volumeThreshold).toBe(10);
});
it('should handle multi-level nested classes', async () => {
class PoolConfig {
maxConnections!: number;
minConnections!: number;
}
class DatabaseConfig {
host!: string;
port!: number;
pool!: PoolConfig;
}
class AppConfig {
database!: DatabaseConfig;
}
(manager as any).config = {
app: {
database: {
host: 'localhost',
pool: {
maxConnections: 20,
},
},
},
};
const config = manager.bind(AppConfig);
expect(config).toBeInstanceOf(AppConfig);
expect(config.database).toBeInstanceOf(DatabaseConfig);
expect(config.database.host).toBe('localhost');
expect(config.database.port).toBe(5432); // Default
expect(config.database.pool).toBeInstanceOf(PoolConfig);
expect(config.database.pool.maxConnections).toBe(20);
expect(config.database.pool.minConnections).toBe(1); // Default
});
it('should bind nested classes with @DefaultValue decorator', async () => {
// Note: Nested classes need at least one of our decorators (@DefaultValue, @Required, @Validate, @ConfigProperty)
// to be detected as configuration classes. For validation tests, see nested-class-validation.spec.ts
class CircuitBreakerOptions {
timeout!: number;
errorThresholdPercentage!: number;
}
class SampleClientConfig {
baseURL!: string;
circuitBreaker!: CircuitBreakerOptions;
}
(manager as any).config = {
clients: {
sample: {
baseUrl: 'https://api.example.com',
circuitBreaker: {
timeout: 5000,
// errorThresholdPercentage missing - should use default
},
},
},
};
const config = manager.bind(SampleClientConfig);
expect(config).toBeInstanceOf(SampleClientConfig);
expect(config.circuitBreaker).toBeInstanceOf(CircuitBreakerOptions);
expect(config.circuitBreaker.timeout).toBe(5000);
expect(config.circuitBreaker.errorThresholdPercentage).toBe(50); // Default applied
});
it('should throw error for missing required property in nested class', async () => {
class CircuitBreakerOptions {
volumeThreshold!: number;
}
class SampleClientConfig {
baseURL!: string;
circuitBreaker!: CircuitBreakerOptions;
}
(manager as any).config = {
clients: {
sample: {
baseUrl: 'https://api.example.com',
circuitBreaker: {
// Missing volumeThreshold
},
},
},
};
expect(() => {
manager.bind(SampleClientConfig);
}).toThrow("Required configuration property 'circuitBreaker.volumeThreshold' is missing");
});
it('should work without @ConfigProperty when property names match', async () => {
class CircuitBreakerOptions {
timeout!: number;
}
class SampleClientConfig {
baseUrl!: string; // No @ConfigProperty, uses property name
// Note: At least one decorator is needed for TypeScript to emit type metadata
circuitBreaker!: CircuitBreakerOptions; // No @ConfigProperty, uses property name
}
(manager as any).config = {
clients: {
sample: {
baseUrl: 'https://api.example.com',
circuitBreaker: {
timeout: 5000,
},
},
},
};
const config = manager.bind(SampleClientConfig);
expect(config).toBeInstanceOf(SampleClientConfig);
expect(config.baseUrl).toBe('https://api.example.com');
expect(config.circuitBreaker).toBeInstanceOf(CircuitBreakerOptions);
expect(config.circuitBreaker.timeout).toBe(5000);
});
});