266 lines
No EOL
7.9 KiB
TypeScript
266 lines
No EOL
7.9 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
|
import {
|
|
onShutdown,
|
|
Shutdown,
|
|
SHUTDOWN_DEFAULTS,
|
|
} from '../src';
|
|
import type { ShutdownOptions } from '../src/types';
|
|
|
|
describe('Shutdown Comprehensive Tests', () => {
|
|
let shutdownInstance: Shutdown;
|
|
|
|
beforeEach(() => {
|
|
// Reset singleton instance for each test
|
|
(Shutdown as any).instance = null;
|
|
// Create a fresh instance for each test
|
|
shutdownInstance = new Shutdown({ autoRegister: false });
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clean up singleton
|
|
(Shutdown as any).instance = null;
|
|
});
|
|
|
|
describe('Basic Functionality', () => {
|
|
it('should register callbacks', () => {
|
|
const callback = mock(async () => {});
|
|
|
|
shutdownInstance.onShutdown(callback, 50, 'test-callback');
|
|
|
|
// We can't directly check callback count, but we can verify it executes
|
|
expect(callback).toBeDefined();
|
|
});
|
|
|
|
it('should execute callbacks on shutdown', async () => {
|
|
const callback1 = mock(async () => {});
|
|
const callback2 = mock(async () => {});
|
|
|
|
shutdownInstance.onShutdown(callback1, 10, 'callback-1');
|
|
shutdownInstance.onShutdown(callback2, 20, 'callback-2');
|
|
|
|
await shutdownInstance.shutdown();
|
|
|
|
expect(callback1).toHaveBeenCalledTimes(1);
|
|
expect(callback2).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should execute callbacks in priority order', async () => {
|
|
const order: string[] = [];
|
|
|
|
const highPriority = mock(async () => {
|
|
order.push('high');
|
|
});
|
|
const mediumPriority = mock(async () => {
|
|
order.push('medium');
|
|
});
|
|
const lowPriority = mock(async () => {
|
|
order.push('low');
|
|
});
|
|
|
|
// Lower number = higher priority
|
|
shutdownInstance.onShutdown(lowPriority, 30, 'low');
|
|
shutdownInstance.onShutdown(highPriority, 10, 'high');
|
|
shutdownInstance.onShutdown(mediumPriority, 20, 'medium');
|
|
|
|
await shutdownInstance.shutdown();
|
|
|
|
expect(order).toEqual(['high', 'medium', 'low']);
|
|
});
|
|
|
|
it('should handle errors in callbacks', async () => {
|
|
const successCallback = mock(async () => {});
|
|
const errorCallback = mock(async () => {
|
|
throw new Error('Callback error');
|
|
});
|
|
|
|
shutdownInstance.onShutdown(successCallback, 10, 'success');
|
|
shutdownInstance.onShutdown(errorCallback, 20, 'error');
|
|
|
|
// Should not throw even if callbacks fail
|
|
await expect(shutdownInstance.shutdown()).resolves.toBeUndefined();
|
|
|
|
expect(successCallback).toHaveBeenCalledTimes(1);
|
|
expect(errorCallback).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should only shutdown once', async () => {
|
|
const callback = mock(async () => {});
|
|
shutdownInstance.onShutdown(callback);
|
|
|
|
await shutdownInstance.shutdown();
|
|
await shutdownInstance.shutdown();
|
|
await shutdownInstance.shutdown();
|
|
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should not register callbacks during shutdown', async () => {
|
|
const firstCallback = mock(async () => {
|
|
// Try to register another callback during shutdown
|
|
shutdownInstance.onShutdown(async () => {
|
|
throw new Error('Should not execute');
|
|
});
|
|
});
|
|
|
|
shutdownInstance.onShutdown(firstCallback);
|
|
|
|
await shutdownInstance.shutdown();
|
|
|
|
expect(firstCallback).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('Timeout Handling', () => {
|
|
it('should timeout if callbacks take too long', async () => {
|
|
const slowShutdown = new Shutdown({
|
|
timeout: 100,
|
|
autoRegister: false
|
|
});
|
|
|
|
const slowCallback = mock(async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
});
|
|
|
|
slowShutdown.onShutdown(slowCallback, 10, 'slow');
|
|
|
|
const startTime = Date.now();
|
|
await expect(slowShutdown.shutdown()).rejects.toThrow('Shutdown timeout');
|
|
const duration = Date.now() - startTime;
|
|
|
|
expect(duration).toBeLessThan(150);
|
|
});
|
|
|
|
it('should complete if callbacks finish before timeout', async () => {
|
|
const quickShutdown = new Shutdown({
|
|
timeout: 1000,
|
|
autoRegister: false
|
|
});
|
|
|
|
const quickCallback = mock(async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
});
|
|
|
|
quickShutdown.onShutdown(quickCallback);
|
|
|
|
await expect(quickShutdown.shutdown()).resolves.toBeUndefined();
|
|
expect(quickCallback).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('Singleton Pattern', () => {
|
|
it('should return same instance via getInstance', () => {
|
|
const instance1 = Shutdown.getInstance();
|
|
const instance2 = Shutdown.getInstance();
|
|
|
|
expect(instance1).toBe(instance2);
|
|
});
|
|
|
|
it('should maintain state across getInstance calls', async () => {
|
|
const instance = Shutdown.getInstance();
|
|
const callback = mock(async () => {});
|
|
|
|
instance.onShutdown(callback);
|
|
|
|
const sameInstance = Shutdown.getInstance();
|
|
await sameInstance.shutdown();
|
|
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('Global onShutdown Function', () => {
|
|
it('should use singleton instance', async () => {
|
|
const callback = mock(async () => {});
|
|
|
|
// Use global function
|
|
onShutdown(callback, 50, 'global-callback');
|
|
|
|
// Get instance and shutdown
|
|
const instance = Shutdown.getInstance();
|
|
await instance.shutdown();
|
|
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should handle different parameter orders', () => {
|
|
const callback = mock(async () => {});
|
|
|
|
// Test with all parameters
|
|
onShutdown(callback, 10, 'with-all-params');
|
|
|
|
// Test with default priority
|
|
onShutdown(callback);
|
|
|
|
// Test with priority but no name
|
|
onShutdown(callback, 20);
|
|
|
|
expect(callback).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle synchronous callbacks', async () => {
|
|
const syncCallback = mock(() => {
|
|
// Synchronous callback
|
|
});
|
|
|
|
shutdownInstance.onShutdown(syncCallback as any, 10, 'sync');
|
|
|
|
await shutdownInstance.shutdown();
|
|
|
|
expect(syncCallback).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should handle callbacks that throw non-Error objects', async () => {
|
|
const throwingCallback = mock(async () => {
|
|
throw 'string error';
|
|
});
|
|
|
|
shutdownInstance.onShutdown(throwingCallback);
|
|
|
|
// Should not throw
|
|
await expect(shutdownInstance.shutdown()).resolves.toBeUndefined();
|
|
expect(throwingCallback).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should handle empty callback list', async () => {
|
|
// No callbacks registered
|
|
await expect(shutdownInstance.shutdown()).resolves.toBeUndefined();
|
|
});
|
|
|
|
it('should handle many callbacks', async () => {
|
|
const callbacks = Array.from({ length: 50 }, (_, i) =>
|
|
mock(async () => {})
|
|
);
|
|
|
|
callbacks.forEach((cb, i) => {
|
|
shutdownInstance.onShutdown(cb, i, `callback-${i}`);
|
|
});
|
|
|
|
await shutdownInstance.shutdown();
|
|
|
|
callbacks.forEach(cb => {
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Default Values', () => {
|
|
it('should use default priority', async () => {
|
|
const callback = mock(async () => {});
|
|
|
|
// No priority specified
|
|
shutdownInstance.onShutdown(callback);
|
|
|
|
await shutdownInstance.shutdown();
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should use default timeout', () => {
|
|
const defaultShutdown = new Shutdown();
|
|
|
|
// Can't directly test timeout value, but we can verify it doesn't throw
|
|
expect(() => new Shutdown()).not.toThrow();
|
|
});
|
|
});
|
|
}); |