stock-bot/libs/core/di/test/lifecycle.test.ts
2025-06-25 11:38:23 -04:00

262 lines
7.4 KiB
TypeScript

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();
});
});
});