180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
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
|
|
// });
|
|
// });
|
|
});
|