tests
This commit is contained in:
parent
3a7254708e
commit
b63e58784c
41 changed files with 5762 additions and 4477 deletions
|
|
@ -1,424 +1,426 @@
|
|||
import { describe, expect, it, beforeEach, afterEach, mock } from 'bun:test';
|
||||
import {
|
||||
Shutdown,
|
||||
onShutdown,
|
||||
onShutdownHigh,
|
||||
onShutdownMedium,
|
||||
onShutdownLow,
|
||||
setShutdownTimeout,
|
||||
isShuttingDown,
|
||||
isShutdownSignalReceived,
|
||||
getShutdownCallbackCount,
|
||||
initiateShutdown,
|
||||
resetShutdown,
|
||||
} from '../src';
|
||||
import type { ShutdownOptions, ShutdownResult } from '../src/types';
|
||||
|
||||
describe('Shutdown Comprehensive Tests', () => {
|
||||
beforeEach(() => {
|
||||
// Reset before each test
|
||||
resetShutdown();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up after each test
|
||||
resetShutdown();
|
||||
});
|
||||
|
||||
describe('Global Functions', () => {
|
||||
describe('onShutdown', () => {
|
||||
it('should register callback with custom priority', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdown(callback, 'custom-handler', 25);
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle callback without name', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdown(callback);
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Priority convenience functions', () => {
|
||||
it('should register high priority callback', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdownHigh(callback, 'high-priority');
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should register medium priority callback', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdownMedium(callback, 'medium-priority');
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should register low priority callback', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdownLow(callback, 'low-priority');
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should execute callbacks in priority order', async () => {
|
||||
const order: string[] = [];
|
||||
|
||||
const highCallback = mock(async () => {
|
||||
order.push('high');
|
||||
});
|
||||
const mediumCallback = mock(async () => {
|
||||
order.push('medium');
|
||||
});
|
||||
const lowCallback = mock(async () => {
|
||||
order.push('low');
|
||||
});
|
||||
|
||||
onShutdownLow(lowCallback, 'low');
|
||||
onShutdownHigh(highCallback, 'high');
|
||||
onShutdownMedium(mediumCallback, 'medium');
|
||||
|
||||
await initiateShutdown();
|
||||
|
||||
expect(order).toEqual(['high', 'medium', 'low']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setShutdownTimeout', () => {
|
||||
it('should set custom timeout', () => {
|
||||
setShutdownTimeout(10000);
|
||||
|
||||
// Timeout is set internally, we can't directly verify it
|
||||
// but we can test it works by using a long-running callback
|
||||
expect(() => setShutdownTimeout(10000)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle negative timeout values', () => {
|
||||
// Should throw for negative values
|
||||
expect(() => setShutdownTimeout(-1000)).toThrow('Shutdown timeout must be positive');
|
||||
});
|
||||
|
||||
it('should handle zero timeout', () => {
|
||||
// Should throw for zero timeout
|
||||
expect(() => setShutdownTimeout(0)).toThrow('Shutdown timeout must be positive');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Status functions', () => {
|
||||
it('should report shutting down status correctly', async () => {
|
||||
expect(isShuttingDown()).toBe(false);
|
||||
|
||||
const promise = initiateShutdown();
|
||||
expect(isShuttingDown()).toBe(true);
|
||||
|
||||
await promise;
|
||||
// Still true after completion
|
||||
expect(isShuttingDown()).toBe(true);
|
||||
|
||||
resetShutdown();
|
||||
expect(isShuttingDown()).toBe(false);
|
||||
});
|
||||
|
||||
it('should track shutdown signal', () => {
|
||||
expect(isShutdownSignalReceived()).toBe(false);
|
||||
|
||||
// Simulate signal by setting global
|
||||
(global as any).__SHUTDOWN_SIGNAL_RECEIVED__ = true;
|
||||
expect(isShutdownSignalReceived()).toBe(true);
|
||||
|
||||
// Clean up
|
||||
delete (global as any).__SHUTDOWN_SIGNAL_RECEIVED__;
|
||||
});
|
||||
|
||||
it('should count callbacks correctly', () => {
|
||||
expect(getShutdownCallbackCount()).toBe(0);
|
||||
|
||||
onShutdown(async () => {});
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
|
||||
onShutdownHigh(async () => {});
|
||||
onShutdownMedium(async () => {});
|
||||
onShutdownLow(async () => {});
|
||||
expect(getShutdownCallbackCount()).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initiateShutdown', () => {
|
||||
it('should execute all callbacks', async () => {
|
||||
const callback1 = mock(async () => {});
|
||||
const callback2 = mock(async () => {});
|
||||
const callback3 = mock(async () => {});
|
||||
|
||||
onShutdown(callback1);
|
||||
onShutdown(callback2);
|
||||
onShutdown(callback3);
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).toHaveBeenCalledTimes(1);
|
||||
expect(callback3).toHaveBeenCalledTimes(1);
|
||||
expect(result.callbacksExecuted).toBe(3);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle errors in callbacks', async () => {
|
||||
const successCallback = mock(async () => {});
|
||||
const errorCallback = mock(async () => {
|
||||
throw new Error('Callback error');
|
||||
});
|
||||
|
||||
onShutdown(successCallback, 'success-handler');
|
||||
onShutdown(errorCallback, 'error-handler');
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(2);
|
||||
expect(result.callbacksFailed).toBe(1);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('1 callbacks failed');
|
||||
});
|
||||
|
||||
it('should only execute once', async () => {
|
||||
const callback = mock(async () => {});
|
||||
onShutdown(callback);
|
||||
|
||||
await initiateShutdown();
|
||||
await initiateShutdown();
|
||||
await initiateShutdown();
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shutdown Class Direct Usage', () => {
|
||||
it('should create instance with options', () => {
|
||||
const options: ShutdownOptions = {
|
||||
timeout: 5000,
|
||||
autoRegister: false,
|
||||
};
|
||||
|
||||
const shutdown = new Shutdown(options);
|
||||
expect(shutdown).toBeInstanceOf(Shutdown);
|
||||
});
|
||||
|
||||
it('should handle concurrent callback registration', () => {
|
||||
const shutdown = new Shutdown();
|
||||
const callbacks = Array.from({ length: 10 }, (_, i) =>
|
||||
mock(async () => {})
|
||||
);
|
||||
|
||||
// Register callbacks concurrently
|
||||
callbacks.forEach((cb, i) => {
|
||||
shutdown.onShutdown(cb, `handler-${i}`, i * 10);
|
||||
});
|
||||
|
||||
expect(shutdown.getCallbackCount()).toBe(10);
|
||||
});
|
||||
|
||||
it('should handle empty callback list', async () => {
|
||||
const shutdown = new Shutdown();
|
||||
|
||||
const result = await shutdown.shutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(0);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should respect timeout', async () => {
|
||||
const shutdown = new Shutdown({ timeout: 100 });
|
||||
|
||||
const slowCallback = mock(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
});
|
||||
|
||||
shutdown.onShutdown(slowCallback, 'slow-handler');
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await shutdown.shutdown();
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
expect(duration).toBeLessThan(150); // Should timeout before 200ms
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Shutdown timeout');
|
||||
});
|
||||
|
||||
it('should handle synchronous callbacks', async () => {
|
||||
const shutdown = new Shutdown();
|
||||
|
||||
const syncCallback = mock(() => {
|
||||
// Synchronous callback
|
||||
return undefined;
|
||||
});
|
||||
|
||||
shutdown.onShutdown(syncCallback as any, 'sync-handler');
|
||||
|
||||
const result = await shutdown.shutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(1);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
expect(syncCallback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle callback that adds more callbacks', async () => {
|
||||
const addingCallback = mock(async () => {
|
||||
// Try to add callback during shutdown
|
||||
onShutdown(async () => {
|
||||
// This should not execute
|
||||
});
|
||||
});
|
||||
|
||||
onShutdown(addingCallback);
|
||||
|
||||
const countBefore = getShutdownCallbackCount();
|
||||
await initiateShutdown();
|
||||
|
||||
// The new callback should not be executed in this shutdown
|
||||
expect(addingCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle very large number of callbacks', async () => {
|
||||
const callbacks = Array.from({ length: 100 }, (_, i) =>
|
||||
mock(async () => {})
|
||||
);
|
||||
|
||||
callbacks.forEach((cb, i) => {
|
||||
onShutdown(cb, `handler-${i}`, i);
|
||||
});
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(100);
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(100);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
|
||||
callbacks.forEach(cb => {
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle callbacks with same priority', async () => {
|
||||
const order: string[] = [];
|
||||
|
||||
const callback1 = mock(async () => { order.push('1'); });
|
||||
const callback2 = mock(async () => { order.push('2'); });
|
||||
const callback3 = mock(async () => { order.push('3'); });
|
||||
|
||||
// All with same priority
|
||||
onShutdown(callback1, 'handler-1', 50);
|
||||
onShutdown(callback2, 'handler-2', 50);
|
||||
onShutdown(callback3, 'handler-3', 50);
|
||||
|
||||
await initiateShutdown();
|
||||
|
||||
// Should execute all, order between same priority is not guaranteed
|
||||
expect(order).toHaveLength(3);
|
||||
expect(order).toContain('1');
|
||||
expect(order).toContain('2');
|
||||
expect(order).toContain('3');
|
||||
});
|
||||
|
||||
it('should handle callback that throws non-Error', async () => {
|
||||
const throwingCallback = mock(async () => {
|
||||
throw 'string error'; // Non-Error thrown
|
||||
});
|
||||
|
||||
onShutdown(throwingCallback, 'throwing-handler');
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksFailed).toBe(1);
|
||||
expect(result.error).toContain('1 callbacks failed');
|
||||
});
|
||||
|
||||
it('should handle undefined callback name', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdown(callback, undefined as any);
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ShutdownResult Accuracy', () => {
|
||||
it('should provide accurate timing information', async () => {
|
||||
const delays = [10, 20, 30];
|
||||
const callbacks = delays.map((delay, i) =>
|
||||
mock(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
})
|
||||
);
|
||||
|
||||
callbacks.forEach((cb, i) => {
|
||||
onShutdown(cb, `timer-${i}`);
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await initiateShutdown();
|
||||
const totalTime = Date.now() - startTime;
|
||||
|
||||
expect(result.duration).toBeGreaterThan(0);
|
||||
expect(result.duration).toBeLessThanOrEqual(totalTime);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should track individual callback execution', async () => {
|
||||
const successCount = 3;
|
||||
const errorCount = 2;
|
||||
|
||||
for (let i = 0; i < successCount; i++) {
|
||||
onShutdown(async () => {}, `success-${i}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < errorCount; i++) {
|
||||
onShutdown(async () => {
|
||||
throw new Error(`Error ${i}`);
|
||||
}, `error-${i}`);
|
||||
}
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(successCount + errorCount);
|
||||
expect(result.callbacksFailed).toBe(errorCount);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain(`${errorCount} callbacks failed`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global State Management', () => {
|
||||
it('should properly reset global state', () => {
|
||||
// Add some callbacks
|
||||
onShutdown(async () => {});
|
||||
onShutdownHigh(async () => {});
|
||||
onShutdownLow(async () => {});
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(3);
|
||||
|
||||
resetShutdown();
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(0);
|
||||
expect(isShuttingDown()).toBe(false);
|
||||
});
|
||||
|
||||
it('should maintain singleton across imports', () => {
|
||||
const instance1 = Shutdown.getInstance();
|
||||
const instance2 = Shutdown.getInstance();
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
});
|
||||
});
|
||||
});
|
||||
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
||||
import {
|
||||
getShutdownCallbackCount,
|
||||
initiateShutdown,
|
||||
isShutdownSignalReceived,
|
||||
isShuttingDown,
|
||||
onShutdown,
|
||||
onShutdownHigh,
|
||||
onShutdownLow,
|
||||
onShutdownMedium,
|
||||
resetShutdown,
|
||||
setShutdownTimeout,
|
||||
Shutdown,
|
||||
} from '../src';
|
||||
import type { ShutdownOptions, ShutdownResult } from '../src/types';
|
||||
|
||||
describe('Shutdown Comprehensive Tests', () => {
|
||||
beforeEach(() => {
|
||||
// Reset before each test
|
||||
resetShutdown();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up after each test
|
||||
resetShutdown();
|
||||
});
|
||||
|
||||
describe('Global Functions', () => {
|
||||
describe('onShutdown', () => {
|
||||
it('should register callback with custom priority', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdown(callback, 'custom-handler', 25);
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle callback without name', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdown(callback);
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Priority convenience functions', () => {
|
||||
it('should register high priority callback', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdownHigh(callback, 'high-priority');
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should register medium priority callback', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdownMedium(callback, 'medium-priority');
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should register low priority callback', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdownLow(callback, 'low-priority');
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should execute callbacks in priority order', async () => {
|
||||
const order: string[] = [];
|
||||
|
||||
const highCallback = mock(async () => {
|
||||
order.push('high');
|
||||
});
|
||||
const mediumCallback = mock(async () => {
|
||||
order.push('medium');
|
||||
});
|
||||
const lowCallback = mock(async () => {
|
||||
order.push('low');
|
||||
});
|
||||
|
||||
onShutdownLow(lowCallback, 'low');
|
||||
onShutdownHigh(highCallback, 'high');
|
||||
onShutdownMedium(mediumCallback, 'medium');
|
||||
|
||||
await initiateShutdown();
|
||||
|
||||
expect(order).toEqual(['high', 'medium', 'low']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setShutdownTimeout', () => {
|
||||
it('should set custom timeout', () => {
|
||||
setShutdownTimeout(10000);
|
||||
|
||||
// Timeout is set internally, we can't directly verify it
|
||||
// but we can test it works by using a long-running callback
|
||||
expect(() => setShutdownTimeout(10000)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle negative timeout values', () => {
|
||||
// Should throw for negative values
|
||||
expect(() => setShutdownTimeout(-1000)).toThrow('Shutdown timeout must be positive');
|
||||
});
|
||||
|
||||
it('should handle zero timeout', () => {
|
||||
// Should throw for zero timeout
|
||||
expect(() => setShutdownTimeout(0)).toThrow('Shutdown timeout must be positive');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Status functions', () => {
|
||||
it('should report shutting down status correctly', async () => {
|
||||
expect(isShuttingDown()).toBe(false);
|
||||
|
||||
const promise = initiateShutdown();
|
||||
expect(isShuttingDown()).toBe(true);
|
||||
|
||||
await promise;
|
||||
// Still true after completion
|
||||
expect(isShuttingDown()).toBe(true);
|
||||
|
||||
resetShutdown();
|
||||
expect(isShuttingDown()).toBe(false);
|
||||
});
|
||||
|
||||
it('should track shutdown signal', () => {
|
||||
expect(isShutdownSignalReceived()).toBe(false);
|
||||
|
||||
// Simulate signal by setting global
|
||||
(global as any).__SHUTDOWN_SIGNAL_RECEIVED__ = true;
|
||||
expect(isShutdownSignalReceived()).toBe(true);
|
||||
|
||||
// Clean up
|
||||
delete (global as any).__SHUTDOWN_SIGNAL_RECEIVED__;
|
||||
});
|
||||
|
||||
it('should count callbacks correctly', () => {
|
||||
expect(getShutdownCallbackCount()).toBe(0);
|
||||
|
||||
onShutdown(async () => {});
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
|
||||
onShutdownHigh(async () => {});
|
||||
onShutdownMedium(async () => {});
|
||||
onShutdownLow(async () => {});
|
||||
expect(getShutdownCallbackCount()).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initiateShutdown', () => {
|
||||
it('should execute all callbacks', async () => {
|
||||
const callback1 = mock(async () => {});
|
||||
const callback2 = mock(async () => {});
|
||||
const callback3 = mock(async () => {});
|
||||
|
||||
onShutdown(callback1);
|
||||
onShutdown(callback2);
|
||||
onShutdown(callback3);
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).toHaveBeenCalledTimes(1);
|
||||
expect(callback3).toHaveBeenCalledTimes(1);
|
||||
expect(result.callbacksExecuted).toBe(3);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle errors in callbacks', async () => {
|
||||
const successCallback = mock(async () => {});
|
||||
const errorCallback = mock(async () => {
|
||||
throw new Error('Callback error');
|
||||
});
|
||||
|
||||
onShutdown(successCallback, 'success-handler');
|
||||
onShutdown(errorCallback, 'error-handler');
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(2);
|
||||
expect(result.callbacksFailed).toBe(1);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('1 callbacks failed');
|
||||
});
|
||||
|
||||
it('should only execute once', async () => {
|
||||
const callback = mock(async () => {});
|
||||
onShutdown(callback);
|
||||
|
||||
await initiateShutdown();
|
||||
await initiateShutdown();
|
||||
await initiateShutdown();
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shutdown Class Direct Usage', () => {
|
||||
it('should create instance with options', () => {
|
||||
const options: ShutdownOptions = {
|
||||
timeout: 5000,
|
||||
autoRegister: false,
|
||||
};
|
||||
|
||||
const shutdown = new Shutdown(options);
|
||||
expect(shutdown).toBeInstanceOf(Shutdown);
|
||||
});
|
||||
|
||||
it('should handle concurrent callback registration', () => {
|
||||
const shutdown = new Shutdown();
|
||||
const callbacks = Array.from({ length: 10 }, (_, i) => mock(async () => {}));
|
||||
|
||||
// Register callbacks concurrently
|
||||
callbacks.forEach((cb, i) => {
|
||||
shutdown.onShutdown(cb, `handler-${i}`, i * 10);
|
||||
});
|
||||
|
||||
expect(shutdown.getCallbackCount()).toBe(10);
|
||||
});
|
||||
|
||||
it('should handle empty callback list', async () => {
|
||||
const shutdown = new Shutdown();
|
||||
|
||||
const result = await shutdown.shutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(0);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should respect timeout', async () => {
|
||||
const shutdown = new Shutdown({ timeout: 100 });
|
||||
|
||||
const slowCallback = mock(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
});
|
||||
|
||||
shutdown.onShutdown(slowCallback, 'slow-handler');
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await shutdown.shutdown();
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
expect(duration).toBeLessThan(150); // Should timeout before 200ms
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Shutdown timeout');
|
||||
});
|
||||
|
||||
it('should handle synchronous callbacks', async () => {
|
||||
const shutdown = new Shutdown();
|
||||
|
||||
const syncCallback = mock(() => {
|
||||
// Synchronous callback
|
||||
return undefined;
|
||||
});
|
||||
|
||||
shutdown.onShutdown(syncCallback as any, 'sync-handler');
|
||||
|
||||
const result = await shutdown.shutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(1);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
expect(syncCallback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle callback that adds more callbacks', async () => {
|
||||
const addingCallback = mock(async () => {
|
||||
// Try to add callback during shutdown
|
||||
onShutdown(async () => {
|
||||
// This should not execute
|
||||
});
|
||||
});
|
||||
|
||||
onShutdown(addingCallback);
|
||||
|
||||
const countBefore = getShutdownCallbackCount();
|
||||
await initiateShutdown();
|
||||
|
||||
// The new callback should not be executed in this shutdown
|
||||
expect(addingCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle very large number of callbacks', async () => {
|
||||
const callbacks = Array.from({ length: 100 }, (_, i) => mock(async () => {}));
|
||||
|
||||
callbacks.forEach((cb, i) => {
|
||||
onShutdown(cb, `handler-${i}`, i);
|
||||
});
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(100);
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(100);
|
||||
expect(result.callbacksFailed).toBe(0);
|
||||
|
||||
callbacks.forEach(cb => {
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle callbacks with same priority', async () => {
|
||||
const order: string[] = [];
|
||||
|
||||
const callback1 = mock(async () => {
|
||||
order.push('1');
|
||||
});
|
||||
const callback2 = mock(async () => {
|
||||
order.push('2');
|
||||
});
|
||||
const callback3 = mock(async () => {
|
||||
order.push('3');
|
||||
});
|
||||
|
||||
// All with same priority
|
||||
onShutdown(callback1, 'handler-1', 50);
|
||||
onShutdown(callback2, 'handler-2', 50);
|
||||
onShutdown(callback3, 'handler-3', 50);
|
||||
|
||||
await initiateShutdown();
|
||||
|
||||
// Should execute all, order between same priority is not guaranteed
|
||||
expect(order).toHaveLength(3);
|
||||
expect(order).toContain('1');
|
||||
expect(order).toContain('2');
|
||||
expect(order).toContain('3');
|
||||
});
|
||||
|
||||
it('should handle callback that throws non-Error', async () => {
|
||||
const throwingCallback = mock(async () => {
|
||||
throw 'string error'; // Non-Error thrown
|
||||
});
|
||||
|
||||
onShutdown(throwingCallback, 'throwing-handler');
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksFailed).toBe(1);
|
||||
expect(result.error).toContain('1 callbacks failed');
|
||||
});
|
||||
|
||||
it('should handle undefined callback name', () => {
|
||||
const callback = mock(async () => {});
|
||||
|
||||
onShutdown(callback, undefined as any);
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ShutdownResult Accuracy', () => {
|
||||
it('should provide accurate timing information', async () => {
|
||||
const delays = [10, 20, 30];
|
||||
const callbacks = delays.map((delay, i) =>
|
||||
mock(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
})
|
||||
);
|
||||
|
||||
callbacks.forEach((cb, i) => {
|
||||
onShutdown(cb, `timer-${i}`);
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await initiateShutdown();
|
||||
const totalTime = Date.now() - startTime;
|
||||
|
||||
expect(result.duration).toBeGreaterThan(0);
|
||||
expect(result.duration).toBeLessThanOrEqual(totalTime);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should track individual callback execution', async () => {
|
||||
const successCount = 3;
|
||||
const errorCount = 2;
|
||||
|
||||
for (let i = 0; i < successCount; i++) {
|
||||
onShutdown(async () => {}, `success-${i}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < errorCount; i++) {
|
||||
onShutdown(async () => {
|
||||
throw new Error(`Error ${i}`);
|
||||
}, `error-${i}`);
|
||||
}
|
||||
|
||||
const result = await initiateShutdown();
|
||||
|
||||
expect(result.callbacksExecuted).toBe(successCount + errorCount);
|
||||
expect(result.callbacksFailed).toBe(errorCount);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain(`${errorCount} callbacks failed`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global State Management', () => {
|
||||
it('should properly reset global state', () => {
|
||||
// Add some callbacks
|
||||
onShutdown(async () => {});
|
||||
onShutdownHigh(async () => {});
|
||||
onShutdownLow(async () => {});
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(3);
|
||||
|
||||
resetShutdown();
|
||||
|
||||
expect(getShutdownCallbackCount()).toBe(0);
|
||||
expect(isShuttingDown()).toBe(false);
|
||||
});
|
||||
|
||||
it('should maintain singleton across imports', () => {
|
||||
const instance1 = Shutdown.getInstance();
|
||||
const instance2 = Shutdown.getInstance();
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue