// 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 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', ); }); }); });