262 lines
7.4 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|