Files
flyer-crawler.projectium.com/src/services/worker.test.ts
2026-02-18 10:48:03 +05:00

177 lines
5.3 KiB
TypeScript

// src/services/worker.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// --- Hoisted Mocks ---
const mocks = vi.hoisted(() => {
return {
gracefulShutdown: vi.fn(),
logger: {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
},
// Mock process events
processOn: vi.fn(),
processExit: vi.fn(),
};
});
// --- Mock Modules ---
vi.mock('./workers.server', () => ({
gracefulShutdown: mocks.gracefulShutdown,
}));
vi.mock('./logger.server', () => ({
logger: mocks.logger,
}));
describe('Worker Entry Point', () => {
let originalProcessOn: typeof process.on;
let originalProcessExit: typeof process.exit;
let eventHandlers: Record<string, (...args: any[]) => void> = {};
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules(); // This is key to re-run the top-level code in worker.ts
// Reset default mock implementations
mocks.gracefulShutdown.mockResolvedValue(undefined);
// Spy on and mock process methods
originalProcessOn = process.on;
originalProcessExit = process.exit;
// Capture event handlers registered with process.on
eventHandlers = {};
process.on = vi.fn((event, listener) => {
eventHandlers[event] = listener;
return process;
}) as any;
process.exit = mocks.processExit as any;
});
afterEach(() => {
// Restore original process methods
process.on = originalProcessOn;
process.exit = originalProcessExit;
});
it('should log initialization messages on import', async () => {
// Act: Import the module to trigger top-level code
await import('./worker');
// Assert
expect(mocks.logger.info).toHaveBeenCalledWith('[Worker] Initializing worker process...');
expect(mocks.logger.info).toHaveBeenCalledWith(
'[Worker] Worker process is running and listening for jobs.',
);
});
it('should register handlers for SIGINT, SIGTERM, uncaughtException, and unhandledRejection', async () => {
// Act
await import('./worker');
// Assert
expect(process.on).toHaveBeenCalledWith('SIGINT', expect.any(Function));
expect(process.on).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
expect(process.on).toHaveBeenCalledWith('uncaughtException', expect.any(Function));
expect(process.on).toHaveBeenCalledWith('unhandledRejection', expect.any(Function));
});
describe('Shutdown Handling', () => {
it('should call gracefulShutdown on SIGINT', async () => {
// Arrange
await import('./worker');
const sigintHandler = eventHandlers['SIGINT'];
expect(sigintHandler).toBeDefined();
// Act
sigintHandler();
// Assert
expect(mocks.logger.info).toHaveBeenCalledWith(
'[Worker] Received SIGINT. Initiating graceful shutdown...',
);
expect(mocks.gracefulShutdown).toHaveBeenCalledWith('SIGINT');
});
it('should call gracefulShutdown on SIGTERM', async () => {
// Arrange
await import('./worker');
const sigtermHandler = eventHandlers['SIGTERM'];
expect(sigtermHandler).toBeDefined();
// Act
sigtermHandler();
// Assert
expect(mocks.logger.info).toHaveBeenCalledWith(
'[Worker] Received SIGTERM. Initiating graceful shutdown...',
);
expect(mocks.gracefulShutdown).toHaveBeenCalledWith('SIGTERM');
});
it('should log an error and exit if gracefulShutdown rejects', async () => {
// Arrange
const shutdownError = new Error('Shutdown failed');
mocks.gracefulShutdown.mockRejectedValue(shutdownError);
await import('./worker');
const sigintHandler = eventHandlers['SIGINT'];
// Act
// The handler catches the rejection, so we don't need to wrap this in expect().rejects
await sigintHandler();
// Assert
expect(mocks.logger.error).toHaveBeenCalledWith(
{ err: shutdownError },
'[Worker] Error during shutdown.',
);
expect(mocks.processExit).toHaveBeenCalledWith(1);
});
});
describe('Error Handling', () => {
it('should log uncaught exceptions', async () => {
// Arrange
await import('./worker');
const exceptionHandler = eventHandlers['uncaughtException'];
expect(exceptionHandler).toBeDefined();
const testError = new Error('Test uncaught exception');
// Act
exceptionHandler(testError);
// Assert
expect(mocks.logger.error).toHaveBeenCalledWith(
{ err: testError },
'[Worker] Uncaught exception',
);
});
it('should log unhandled promise rejections', async () => {
// Arrange
await import('./worker');
const rejectionHandler = eventHandlers['unhandledRejection'];
expect(rejectionHandler).toBeDefined();
const testReason = 'Promise rejected';
const testPromise = Promise.reject(testReason);
// We must handle this rejection in the test to prevent Vitest/Node from flagging it as unhandled
testPromise.catch((err) => {
console.log('Handled expected test rejection to prevent test runner error:', err);
});
// Act
rejectionHandler(testReason, testPromise);
// Assert
expect(mocks.logger.error).toHaveBeenCalledWith(
{ reason: testReason, promise: testPromise },
'[Worker] Unhandled Rejection',
);
});
});
});