import { asClass, asFunction, asValue, createContainer, InjectionMode } from 'awilix'; import { beforeEach, describe, expect, it, mock } from 'bun:test'; import { ServiceContainerBuilder } from '../src/container/builder'; import { OperationContext } from '../src/operation-context'; import { PoolSizeCalculator } from '../src/pool-size-calculator'; import { HandlerScanner } from '../src/scanner/handler-scanner'; import { ServiceApplication } from '../src/service-application'; 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 }); }); });