Files
flyer-crawler.projectium.com/src/services/queueService.test.ts
2025-12-26 10:32:25 -08:00

118 lines
3.9 KiB
TypeScript

// src/services/queueService.test.ts
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
// --- Hoisted Mocks ---
const mocks = vi.hoisted(() => {
const createMockQueue = (name: string) => ({
name,
close: vi.fn().mockResolvedValue(undefined),
add: vi.fn(),
});
return {
flyerQueue: createMockQueue('flyer-processing'),
emailQueue: createMockQueue('email-sending'),
analyticsQueue: createMockQueue('analytics-reporting'),
weeklyAnalyticsQueue: createMockQueue('weekly-analytics-reporting'),
cleanupQueue: createMockQueue('file-cleanup'),
tokenCleanupQueue: createMockQueue('token-cleanup'),
redisConnection: {
quit: vi.fn().mockResolvedValue('OK'),
},
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
};
});
// --- Mock Modules ---
vi.mock('./queues.server', () => ({
flyerQueue: mocks.flyerQueue,
emailQueue: mocks.emailQueue,
analyticsQueue: mocks.analyticsQueue,
weeklyAnalyticsQueue: mocks.weeklyAnalyticsQueue,
cleanupQueue: mocks.cleanupQueue,
tokenCleanupQueue: mocks.tokenCleanupQueue,
}));
vi.mock('./redis.server', () => ({
connection: mocks.redisConnection,
}));
vi.mock('./logger.server', () => ({
logger: mocks.logger,
}));
// --- Test ---
describe('Queue Service (API Shutdown)', () => {
let gracefulShutdown: (signal: string) => Promise<void>;
let processExitSpy: Mock;
beforeEach(async () => {
vi.clearAllMocks();
vi.resetModules();
// Spy on process.exit and prevent it from actually exiting
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
// Dynamically import the module under test
const queueService = await import('./queueService.server');
gracefulShutdown = queueService.gracefulShutdown;
});
afterEach(() => {
processExitSpy.mockRestore();
});
it('should attempt to close all queues and the redis connection on shutdown', async () => {
await gracefulShutdown('SIGINT');
expect(mocks.flyerQueue.close).toHaveBeenCalledTimes(1);
expect(mocks.emailQueue.close).toHaveBeenCalledTimes(1);
expect(mocks.analyticsQueue.close).toHaveBeenCalledTimes(1);
expect(mocks.cleanupQueue.close).toHaveBeenCalledTimes(1);
expect(mocks.weeklyAnalyticsQueue.close).toHaveBeenCalledTimes(1);
expect(mocks.tokenCleanupQueue.close).toHaveBeenCalledTimes(1);
expect(mocks.redisConnection.quit).toHaveBeenCalledTimes(1);
});
it('should log success and exit with code 0 if all resources close successfully', async () => {
await gracefulShutdown('SIGINT');
expect(mocks.logger.info).toHaveBeenCalledWith(
'[Shutdown] All queues and connections closed successfully.',
);
expect(processExitSpy).toHaveBeenCalledWith(0);
});
it('should log a warning and exit with code 1 if a queue fails to close', async () => {
const closeError = new Error('Queue failed to close');
mocks.emailQueue.close.mockRejectedValue(closeError);
await gracefulShutdown('SIGTERM');
expect(mocks.logger.error).toHaveBeenCalledWith(
{ err: closeError, resource: 'emailQueue' },
'[Shutdown] Error closing resource.',
);
expect(mocks.logger.warn).toHaveBeenCalledWith('[Shutdown] Graceful shutdown completed with errors.');
expect(processExitSpy).toHaveBeenCalledWith(1);
});
it('should log a warning and exit with code 1 if the redis connection fails to close', async () => {
const redisError = new Error('Redis quit failed');
mocks.redisConnection.quit.mockRejectedValue(redisError);
await gracefulShutdown('SIGTERM');
expect(mocks.logger.error).toHaveBeenCalledWith(
{ err: redisError, resource: 'redisConnection' },
'[Shutdown] Error closing resource.',
);
expect(mocks.logger.warn).toHaveBeenCalledWith('[Shutdown] Graceful shutdown completed with errors.');
expect(processExitSpy).toHaveBeenCalledWith(1);
});
});