stock-bot/libs/core/shutdown/test/shutdown-comprehensive.test.ts
2025-06-26 17:30:13 -04:00

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();
});
});
});