import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test'; import { Shutdown } from '../src/shutdown'; describe('Shutdown', () => { let shutdown: Shutdown; beforeEach(() => { // Reset singleton instance for each test (Shutdown as any).instance = null; shutdown = Shutdown.getInstance({ timeout: 1000 }); }); afterEach(() => { // Clean up (Shutdown as any).instance = null; }); describe('getInstance', () => { it('should return singleton instance', () => { const instance1 = Shutdown.getInstance(); const instance2 = Shutdown.getInstance(); expect(instance1).toBe(instance2); }); it('should use provided config on first call', () => { const instance = Shutdown.getInstance({ timeout: 5000 }); expect(instance).toBeDefined(); }); }); describe('handler registration', () => { it('should register high priority handler', () => { const handler = mock(async () => {}); shutdown.onShutdownHigh(handler, 'High Priority Task'); expect(shutdown['callbacks']).toHaveLength(1); expect(shutdown['callbacks'][0].name).toBe('High Priority Task'); expect(shutdown['callbacks'][0].priority).toBe(10); }); it('should register medium priority handler', () => { const handler = mock(async () => {}); shutdown.onShutdownMedium(handler, 'Medium Priority Task'); expect(shutdown['callbacks']).toHaveLength(1); expect(shutdown['callbacks'][0].priority).toBe(50); }); it('should register low priority handler', () => { const handler = mock(async () => {}); shutdown.onShutdownLow(handler, 'Low Priority Task'); expect(shutdown['callbacks']).toHaveLength(1); expect(shutdown['callbacks'][0].priority).toBe(90); }); it('should register multiple handlers in order', () => { const handler1 = mock(async () => {}); const handler2 = mock(async () => {}); const handler3 = mock(async () => {}); shutdown.onShutdownHigh(handler1, 'First'); shutdown.onShutdownHigh(handler2, 'Second'); shutdown.onShutdownHigh(handler3, 'Third'); expect(shutdown['callbacks']).toHaveLength(3); expect(shutdown['callbacks'][0].name).toBe('First'); expect(shutdown['callbacks'][2].name).toBe('Third'); }); }); describe('shutdown process', () => { it('should execute handlers in priority order', async () => { const executionOrder: string[] = []; const highHandler = mock(async () => { executionOrder.push('high'); }); const mediumHandler = mock(async () => { executionOrder.push('medium'); }); const lowHandler = mock(async () => { executionOrder.push('low'); }); shutdown.onShutdownLow(lowHandler, 'Low'); shutdown.onShutdownMedium(mediumHandler, 'Medium'); shutdown.onShutdownHigh(highHandler, 'High'); await shutdown.shutdown(); expect(executionOrder).toEqual(['high', 'medium', 'low']); }); it('should only shutdown once', async () => { const handler = mock(async () => {}); shutdown.onShutdownHigh(handler, 'Handler'); await shutdown.shutdown(); await shutdown.shutdown(); // Second call should be ignored expect(handler).toHaveBeenCalledTimes(1); }); it('should handle errors in handlers', async () => { const errorHandler = mock(async () => { throw new Error('Handler error'); }); const successHandler = mock(async () => {}); shutdown.onShutdownHigh(errorHandler, 'Error Handler'); shutdown.onShutdownHigh(successHandler, 'Success Handler'); await shutdown.shutdown(); expect(errorHandler).toHaveBeenCalled(); expect(successHandler).toHaveBeenCalled(); }); it('should respect timeout', async () => { const slowHandler = mock(async () => { await new Promise(resolve => setTimeout(resolve, 2000)); }); shutdown = Shutdown.getInstance({ timeout: 100 }); shutdown.onShutdownHigh(slowHandler, 'Slow Handler'); const start = Date.now(); await shutdown.shutdown(); const duration = Date.now() - start; // Shutdown waits for timeout (100ms) plus some processing time expect(duration).toBeGreaterThan(90); expect(duration).toBeLessThan(1500); // But not the full 2000ms }); }); describe('reset', () => { it('should clear all handlers', () => { shutdown.onShutdownHigh(async () => {}, 'Handler 1'); shutdown.onShutdownMedium(async () => {}, 'Handler 2'); shutdown.onShutdownLow(async () => {}, 'Handler 3'); // Manually clear callbacks to simulate reset shutdown['callbacks'] = []; expect(shutdown['callbacks']).toHaveLength(0); }); it('should reset shutdown state', async () => { const handler = mock(async () => {}); shutdown.onShutdownHigh(handler, 'Handler'); await shutdown.shutdown(); // Reset by creating new instance (Shutdown as any).instance = null; shutdown = Shutdown.getInstance({ timeout: 1000 }); shutdown.onShutdownHigh(handler, 'Handler'); await shutdown.shutdown(); expect(handler).toHaveBeenCalledTimes(2); }); }); // Skip forceShutdown test as it's not implemented in current shutdown // describe.skip('forceShutdown', () => { // it('should exit process after timeout', async () => { // // Skipped // }); // }); });