import type { AwilixContainer } from 'awilix'; import { beforeEach, describe, expect, it, mock } from 'bun:test'; import { ServiceLifecycleManager } from '../src/utils/lifecycle'; describe('ServiceLifecycleManager', () => { let manager: ServiceLifecycleManager; beforeEach(() => { manager = new ServiceLifecycleManager(); }); describe('initializeServices', () => { it('should initialize services with connect method', async () => { const mockCache = { connect: mock(() => Promise.resolve()), }; const mockMongoClient = { connect: mock(() => Promise.resolve()), }; const mockContainer = { cradle: { cache: mockCache, mongoClient: mockMongoClient, postgresClient: null, // Not configured }, } as unknown as AwilixContainer; await manager.initializeServices(mockContainer); expect(mockCache.connect).toHaveBeenCalled(); expect(mockMongoClient.connect).toHaveBeenCalled(); }); it('should initialize services with initialize method', async () => { const mockService = { initialize: mock(() => Promise.resolve()), }; const mockContainer = { cradle: { cache: mockService, }, } as unknown as AwilixContainer; await manager.initializeServices(mockContainer); expect(mockService.initialize).toHaveBeenCalled(); }); it('should handle initialization errors', async () => { const mockService = { connect: mock(() => Promise.reject(new Error('Connection failed'))), }; const mockContainer = { cradle: { cache: mockService, }, } as unknown as AwilixContainer; await expect(manager.initializeServices(mockContainer)).rejects.toThrow('Connection failed'); }); it('should handle initialization timeout', async () => { const mockService = { connect: mock(() => new Promise(() => {})), // Never resolves }; const mockContainer = { cradle: { cache: mockService, }, } as unknown as AwilixContainer; await expect(manager.initializeServices(mockContainer, 100)).rejects.toThrow( 'cache initialization timed out after 100ms' ); }); }); describe('shutdownServices', () => { it('should shutdown services with disconnect method', async () => { const mockCache = { disconnect: mock(() => Promise.resolve()), }; const mockMongoClient = { disconnect: mock(() => Promise.resolve()), }; const mockContainer = { cradle: { cache: mockCache, mongoClient: mockMongoClient, }, } as unknown as AwilixContainer; await manager.shutdownServices(mockContainer); expect(mockCache.disconnect).toHaveBeenCalled(); expect(mockMongoClient.disconnect).toHaveBeenCalled(); }); it('should shutdown services with close method', async () => { const mockService = { close: mock(() => Promise.resolve()), }; const mockContainer = { cradle: { queueManager: mockService, }, } as unknown as AwilixContainer; await manager.shutdownServices(mockContainer); expect(mockService.close).toHaveBeenCalled(); }); it('should shutdown services with shutdown method', async () => { const mockService = { shutdown: mock(() => Promise.resolve()), }; const mockContainer = { cradle: { cache: mockService, }, } as unknown as AwilixContainer; await manager.shutdownServices(mockContainer); expect(mockService.shutdown).toHaveBeenCalled(); }); it('should handle shutdown errors gracefully', async () => { const mockService = { disconnect: mock(() => Promise.reject(new Error('Disconnect failed'))), }; const mockContainer = { cradle: { cache: mockService, }, } as unknown as AwilixContainer; // Should not throw await manager.shutdownServices(mockContainer); }); it('should shutdown services in reverse order', async () => { const callOrder: string[] = []; const mockCache = { disconnect: mock(() => { callOrder.push('cache'); return Promise.resolve(); }), }; const mockQueueManager = { close: mock(() => { callOrder.push('queue'); return Promise.resolve(); }), }; const mockContainer = { cradle: { cache: mockCache, queueManager: mockQueueManager, }, } as unknown as AwilixContainer; await manager.shutdownServices(mockContainer); // Queue manager should be shutdown before cache (reverse order) expect(callOrder[0]).toBe('queue'); expect(callOrder[1]).toBe('cache'); }); }); describe('mixed lifecycle methods', () => { it('should handle services with multiple lifecycle methods', async () => { const mockService = { connect: mock(() => Promise.resolve()), disconnect: mock(() => Promise.resolve()), initialize: mock(() => Promise.resolve()), shutdown: mock(() => Promise.resolve()), }; const mockContainer = { cradle: { cache: mockService, }, } as unknown as AwilixContainer; // Initialize should prefer connect over initialize await manager.initializeServices(mockContainer); expect(mockService.connect).toHaveBeenCalled(); expect(mockService.initialize).not.toHaveBeenCalled(); // Shutdown should prefer disconnect over others await manager.shutdownServices(mockContainer); expect(mockService.disconnect).toHaveBeenCalled(); expect(mockService.shutdown).not.toHaveBeenCalled(); }); }); describe('complete lifecycle flow', () => { it('should handle full initialization and shutdown cycle', async () => { const mockCache = { connect: mock(() => Promise.resolve()), disconnect: mock(() => Promise.resolve()), }; const mockMongoClient = { connect: mock(() => Promise.resolve()), disconnect: mock(() => Promise.resolve()), }; const mockPostgresClient = { connect: mock(() => Promise.resolve()), close: mock(() => Promise.resolve()), }; const mockQuestdbClient = { initialize: mock(() => Promise.resolve()), shutdown: mock(() => Promise.resolve()), }; const mockContainer = { cradle: { cache: mockCache, mongoClient: mockMongoClient, postgresClient: mockPostgresClient, questdbClient: mockQuestdbClient, proxyManager: null, // Not configured queueManager: null, // Not configured }, } as unknown as AwilixContainer; // Initialize all services await manager.initializeServices(mockContainer); expect(mockCache.connect).toHaveBeenCalled(); expect(mockMongoClient.connect).toHaveBeenCalled(); expect(mockPostgresClient.connect).toHaveBeenCalled(); expect(mockQuestdbClient.initialize).toHaveBeenCalled(); // Shutdown all services await manager.shutdownServices(mockContainer); expect(mockCache.disconnect).toHaveBeenCalled(); expect(mockMongoClient.disconnect).toHaveBeenCalled(); expect(mockPostgresClient.close).toHaveBeenCalled(); expect(mockQuestdbClient.shutdown).toHaveBeenCalled(); }); }); });