264 lines
No EOL
7.8 KiB
TypeScript
264 lines
No EOL
7.8 KiB
TypeScript
import { describe, it, expect, beforeEach, mock } from 'bun:test';
|
|
import { createContainer, InjectionMode, asClass, asFunction, asValue } from 'awilix';
|
|
import { ServiceContainerBuilder } from '../src/container/builder';
|
|
import { ServiceApplication } from '../src/service-application';
|
|
import { HandlerScanner } from '../src/scanner/handler-scanner';
|
|
import { OperationContext } from '../src/operation-context';
|
|
import { PoolSizeCalculator } from '../src/pool-size-calculator';
|
|
|
|
describe('Dependency Injection', () => {
|
|
describe('ServiceContainerBuilder', () => {
|
|
let builder: ServiceContainerBuilder;
|
|
|
|
beforeEach(() => {
|
|
builder = new ServiceContainerBuilder();
|
|
});
|
|
|
|
it('should create container with default configuration', async () => {
|
|
const config = {
|
|
name: 'test-service',
|
|
version: '1.0.0',
|
|
service: {
|
|
name: 'test-service',
|
|
type: 'WORKER' as const,
|
|
serviceName: 'test-service',
|
|
port: 3000,
|
|
},
|
|
log: {
|
|
level: 'info',
|
|
format: 'json',
|
|
},
|
|
};
|
|
|
|
builder.withConfig(config);
|
|
builder.skipInitialization(); // Skip initialization for testing
|
|
|
|
const container = await builder.build();
|
|
expect(container).toBeDefined();
|
|
});
|
|
|
|
it('should configure services', async () => {
|
|
const config = {
|
|
name: 'test-service',
|
|
version: '1.0.0',
|
|
service: {
|
|
name: 'test-service',
|
|
type: 'WORKER' as const,
|
|
serviceName: 'test-service',
|
|
port: 3000,
|
|
},
|
|
log: {
|
|
level: 'info',
|
|
format: 'json',
|
|
},
|
|
};
|
|
|
|
builder
|
|
.withConfig(config)
|
|
.withOptions({
|
|
enableCache: true,
|
|
enableQueue: false,
|
|
})
|
|
.skipInitialization();
|
|
|
|
const container = await builder.build();
|
|
expect(container).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Basic Container Operations', () => {
|
|
it('should register and resolve values', () => {
|
|
const container = createContainer({
|
|
injectionMode: InjectionMode.PROXY,
|
|
});
|
|
|
|
container.register({
|
|
testValue: asValue('test'),
|
|
});
|
|
|
|
expect(container.resolve('testValue')).toBe('test');
|
|
});
|
|
|
|
it('should register and resolve classes', () => {
|
|
class TestClass {
|
|
getValue() {
|
|
return 'test';
|
|
}
|
|
}
|
|
|
|
const container = createContainer({
|
|
injectionMode: InjectionMode.PROXY,
|
|
});
|
|
|
|
container.register({
|
|
testClass: asClass(TestClass),
|
|
});
|
|
|
|
const instance = container.resolve('testClass');
|
|
expect(instance).toBeInstanceOf(TestClass);
|
|
expect(instance.getValue()).toBe('test');
|
|
});
|
|
|
|
it('should handle dependencies', () => {
|
|
const container = createContainer({
|
|
injectionMode: InjectionMode.PROXY,
|
|
});
|
|
|
|
// Test with scoped container
|
|
container.register({
|
|
config: asValue({ host: 'localhost', port: 5432 }),
|
|
connection: asFunction(() => {
|
|
const config = container.resolve('config');
|
|
return `postgresql://${config.host}:${config.port}/mydb`;
|
|
}).scoped(),
|
|
});
|
|
|
|
const connection = container.resolve('connection');
|
|
expect(connection).toBe('postgresql://localhost:5432/mydb');
|
|
});
|
|
});
|
|
|
|
describe('OperationContext', () => {
|
|
it('should create operation context', () => {
|
|
const context = new OperationContext({
|
|
handlerName: 'test-handler',
|
|
operationName: 'test-op',
|
|
});
|
|
|
|
expect(context.traceId).toBeDefined();
|
|
expect(context.logger).toBeDefined();
|
|
expect(context.metadata).toEqual({});
|
|
});
|
|
|
|
it('should include metadata', () => {
|
|
const metadata = { userId: '123', source: 'api' };
|
|
const context = new OperationContext({
|
|
handlerName: 'test-handler',
|
|
operationName: 'test-op',
|
|
metadata,
|
|
});
|
|
|
|
expect(context.metadata).toEqual(metadata);
|
|
});
|
|
|
|
it('should track execution time', async () => {
|
|
const context = new OperationContext({
|
|
handlerName: 'test-handler',
|
|
operationName: 'test-op',
|
|
});
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
|
|
const executionTime = context.getExecutionTime();
|
|
expect(executionTime).toBeGreaterThanOrEqual(10);
|
|
});
|
|
|
|
it('should create child context', () => {
|
|
const parentContext = new OperationContext({
|
|
handlerName: 'parent-handler',
|
|
operationName: 'parent-op',
|
|
metadata: { parentId: '123' },
|
|
});
|
|
|
|
const childContext = parentContext.createChild('child-op', { childId: '456' });
|
|
|
|
expect(childContext.traceId).toBe(parentContext.traceId);
|
|
expect(childContext.metadata).toEqual({ parentId: '123', childId: '456' });
|
|
});
|
|
});
|
|
|
|
describe('HandlerScanner', () => {
|
|
it('should create scanner instance', () => {
|
|
const mockRegistry = {
|
|
register: mock(() => {}),
|
|
getHandlers: mock(() => []),
|
|
};
|
|
|
|
const mockContainer = createContainer({
|
|
injectionMode: InjectionMode.PROXY,
|
|
});
|
|
|
|
const scanner = new HandlerScanner(mockRegistry as any, mockContainer);
|
|
|
|
expect(scanner).toBeDefined();
|
|
expect(scanner.scanHandlers).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('ServiceApplication', () => {
|
|
it('should create service application', () => {
|
|
const mockConfig = {
|
|
name: 'test-service',
|
|
version: '1.0.0',
|
|
service: {
|
|
name: 'test-service',
|
|
type: 'WORKER' as const,
|
|
serviceName: 'test-service',
|
|
port: 3000,
|
|
},
|
|
log: {
|
|
level: 'info',
|
|
format: 'json',
|
|
},
|
|
};
|
|
|
|
const serviceConfig = {
|
|
serviceName: 'test-service',
|
|
};
|
|
|
|
const app = new ServiceApplication(mockConfig, serviceConfig);
|
|
|
|
expect(app).toBeDefined();
|
|
expect(app.start).toBeDefined();
|
|
expect(app.stop).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Pool Size Calculator', () => {
|
|
it('should calculate pool size for services', () => {
|
|
const recommendation = PoolSizeCalculator.calculate('web-api');
|
|
|
|
expect(recommendation.min).toBe(2);
|
|
expect(recommendation.max).toBe(10);
|
|
expect(recommendation.idle).toBe(2);
|
|
});
|
|
|
|
it('should calculate pool size for handlers', () => {
|
|
const recommendation = PoolSizeCalculator.calculate('data-ingestion', 'batch-import');
|
|
|
|
expect(recommendation.min).toBe(10);
|
|
expect(recommendation.max).toBe(100);
|
|
expect(recommendation.idle).toBe(20);
|
|
});
|
|
|
|
it('should use custom configuration', () => {
|
|
const recommendation = PoolSizeCalculator.calculate('custom', undefined, {
|
|
minConnections: 5,
|
|
maxConnections: 50,
|
|
});
|
|
|
|
expect(recommendation.min).toBe(5);
|
|
expect(recommendation.max).toBe(50);
|
|
expect(recommendation.idle).toBe(13); // (5+50)/4 = 13.75 -> 13
|
|
});
|
|
|
|
it('should fall back to defaults', () => {
|
|
const recommendation = PoolSizeCalculator.calculate('unknown-service');
|
|
|
|
expect(recommendation.min).toBe(2);
|
|
expect(recommendation.max).toBe(10);
|
|
expect(recommendation.idle).toBe(3);
|
|
});
|
|
|
|
it('should calculate optimal pool size', () => {
|
|
const size = PoolSizeCalculator.getOptimalPoolSize(
|
|
100, // 100 requests per second
|
|
50, // 50ms average query time
|
|
100 // 100ms target latency
|
|
);
|
|
|
|
expect(size).toBeGreaterThan(0);
|
|
expect(size).toBe(50); // max(100*0.05*1.2, 100*50/100, 2) = max(6, 50, 2) = 50
|
|
});
|
|
});
|
|
}); |