Skip to main content
Glama
cleanup-handlers.test.ts7.24 kB
import { describe, it } from 'node:test'; import assert from 'node:assert'; import { LSPClient } from '../src/lsp-client.js'; describe('Cleanup Handler Logic', () => { describe('cleanup function behavior', () => { it('should only execute once when called multiple times', () => { const client = new LSPClient(); let cleanupExecutions = 0; let cleanupCalled = false; const cleanup = (signal: string) => { if (cleanupCalled) return; cleanupCalled = true; cleanupExecutions++; client.disconnect(); }; // Call cleanup multiple times with different signals cleanup('SIGINT'); cleanup('SIGTERM'); cleanup('SIGHUP'); cleanup('beforeExit'); assert.strictEqual(cleanupExecutions, 1, 'cleanup should execute exactly once'); assert.strictEqual(client.shouldReconnect(), false, 'client should be shut down'); }); it('should clear reconnection timer during cleanup', () => { let reconnectTimer: NodeJS.Timeout | null = setTimeout(() => {}, 5000); let timerCleared = false; const cleanup = () => { if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; timerCleared = true; } }; cleanup(); assert.strictEqual(timerCleared, true, 'timer should be cleared'); assert.strictEqual(reconnectTimer, null, 'timer reference should be null'); }); it('should reset reconnection flag during cleanup', () => { let isReconnecting = true; const cleanup = () => { isReconnecting = false; }; cleanup(); assert.strictEqual(isReconnecting, false, 'reconnection flag should be reset'); }); it('should disconnect LSP client during cleanup', () => { const client = new LSPClient(); const cleanup = () => { client.disconnect(); }; cleanup(); assert.strictEqual( client.shouldReconnect(), false, 'client should be shut down' ); }); }); describe('comprehensive cleanup sequence', () => { it('should perform full cleanup in correct order', () => { const client = new LSPClient(); let reconnectTimer: NodeJS.Timeout | null = setTimeout(() => {}, 5000); let isReconnecting = true; let cleanupCalled = false; const steps: string[] = []; const cleanup = (signal: string) => { if (cleanupCalled) { steps.push('guard:skip'); return; } cleanupCalled = true; steps.push('guard:pass'); // Stop reconnection loop if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; steps.push('timer:cleared'); } isReconnecting = false; steps.push('flag:reset'); // Disconnect LSP client client.disconnect(); steps.push('client:disconnected'); }; // First cleanup cleanup('SIGINT'); assert.deepStrictEqual( steps, [ 'guard:pass', 'timer:cleared', 'flag:reset', 'client:disconnected', ], 'cleanup steps should execute in order' ); // Second cleanup should be skipped cleanup('SIGTERM'); assert.strictEqual( steps[steps.length - 1], 'guard:skip', 'second cleanup should be guarded' ); }); it('should handle cleanup with null timer gracefully', () => { const client = new LSPClient(); let reconnectTimer: NodeJS.Timeout | null = null; const cleanup = () => { if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } client.disconnect(); }; // Should not throw when timer is null assert.doesNotThrow(() => cleanup()); assert.strictEqual(client.shouldReconnect(), false); }); it('should handle cleanup when already disconnected', () => { const client = new LSPClient(); // Disconnect first client.disconnect(); const cleanup = () => { client.disconnect(); // Call disconnect again }; // Should not throw assert.doesNotThrow(() => cleanup()); assert.strictEqual(client.shouldReconnect(), false); }); }); describe('signal handler scenarios', () => { it('should support multiple signal types', () => { const signals = ['SIGINT', 'SIGTERM', 'SIGHUP', 'beforeExit', 'stdin-close']; const receivedSignals: string[] = []; const cleanup = (signal: string) => { receivedSignals.push(signal); }; signals.forEach((signal) => cleanup(signal)); assert.deepStrictEqual(receivedSignals, signals, 'all signals should be handled'); }); it('should prevent cleanup from running twice on multiple signals', () => { let cleanupCalled = false; let executionCount = 0; const cleanup = () => { if (cleanupCalled) return; cleanupCalled = true; executionCount++; }; // Simulate multiple signals arriving cleanup(); // SIGINT cleanup(); // SIGTERM cleanup(); // SIGHUP assert.strictEqual(executionCount, 1, 'should only execute once'); }); }); describe('state consistency', () => { it('should ensure all state is cleaned up', () => { const client = new LSPClient(); let reconnectTimer: NodeJS.Timeout | null = setTimeout(() => {}, 1000); let isReconnecting = true; let cleanupCalled = false; const cleanup = () => { if (cleanupCalled) return; cleanupCalled = true; if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } isReconnecting = false; client.disconnect(); }; cleanup(); // Verify all state is clean assert.strictEqual(cleanupCalled, true, 'cleanup flag should be set'); assert.strictEqual(reconnectTimer, null, 'timer should be null'); assert.strictEqual(isReconnecting, false, 'reconnecting flag should be false'); assert.strictEqual(client.shouldReconnect(), false, 'client should not reconnect'); }); it('should maintain idempotency across multiple cleanup attempts', () => { const client = new LSPClient(); let reconnectTimer: NodeJS.Timeout | null = setTimeout(() => {}, 1000); let cleanupCalled = false; let disconnectCalls = 0; const cleanup = () => { if (cleanupCalled) return; cleanupCalled = true; if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } // Track disconnect calls if (!client.shouldReconnect()) { disconnectCalls++; } else { client.disconnect(); disconnectCalls++; } }; // Multiple cleanup calls for (let i = 0; i < 5; i++) { cleanup(); } assert.strictEqual(disconnectCalls, 1, 'disconnect should only be called once'); assert.strictEqual(client.shouldReconnect(), false, 'client should be shut down'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ryanmazzolini/minimal-godot-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server