/**
* Queue Polling Race Condition Fix Tests (SEC-001)
*
* Tests for event-driven queue slot notification system that replaces
* the polling loop to fix race conditions and FIFO violations.
*
* @see https://github.com/aberemia24/code-executor-MCP/issues/40
*
* NOTE: These are unit tests that verify the event-driven pattern
* without requiring actual MCP server connections.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ConnectionQueue } from '../src/mcp/connection-queue.js';
import { EventEmitter } from 'events';
describe('Queue Polling Race Condition Fix (SEC-001)', () => {
describe('Event-Driven Pattern (T066)', () => {
let emitter: EventEmitter;
let queue: ConnectionQueue;
beforeEach(() => {
emitter = new EventEmitter();
queue = new ConnectionQueue({
maxSize: 10,
timeoutMs: 5000,
});
});
it('should_useEventEmitter_notPolling', () => {
// Verify EventEmitter is used (not polling with setTimeout)
expect(emitter).toBeInstanceOf(EventEmitter);
expect(emitter.eventNames()).toEqual([]);
});
it('should_emitEvent_when_slotFreed', async () => {
// Simulate event-driven notification
const requestId = 'test-request-1';
let eventFired = false;
emitter.once(`slot-${requestId}`, () => {
eventFired = true;
});
// Simulate slot being freed
emitter.emit(`slot-${requestId}`);
expect(eventFired).toBe(true);
});
it('should_waitForEvent_with_timeout', async () => {
// Simulate waiting for event with timeout protection
const requestId = 'test-request-2';
const timeoutMs = 100;
const waitPromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
emitter.off(`slot-${requestId}`, handler);
reject(new Error('Timeout'));
}, timeoutMs);
const handler = () => {
clearTimeout(timeout);
resolve('success');
};
emitter.once(`slot-${requestId}`, handler);
});
// Don't emit event, let it timeout
await expect(waitPromise).rejects.toThrow('Timeout');
});
it('should_cleanupListener_on_timeout', async () => {
// Verify listener is removed after timeout
const requestId = 'test-request-3';
const timeoutMs = 50;
const waitPromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
emitter.off(`slot-${requestId}`, handler);
reject(new Error('Timeout'));
}, timeoutMs);
const handler = () => {
clearTimeout(timeout);
resolve('success');
};
emitter.once(`slot-${requestId}`, handler);
});
try {
await waitPromise;
} catch {
// Expected timeout
}
// Verify no listeners left
expect(emitter.listenerCount(`slot-${requestId}`)).toBe(0);
});
it('should_cleanupListener_on_success', async () => {
// Verify listener is removed after success
const requestId = 'test-request-4';
const waitPromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
emitter.off(`slot-${requestId}`, handler);
reject(new Error('Timeout'));
}, 1000);
const handler = () => {
clearTimeout(timeout);
resolve('success');
};
emitter.once(`slot-${requestId}`, handler);
});
// Emit event immediately
emitter.emit(`slot-${requestId}`);
await expect(waitPromise).resolves.toBe('success');
// Verify no listeners left (.once() auto-removes)
expect(emitter.listenerCount(`slot-${requestId}`)).toBe(0);
});
});
describe('FIFO Ordering Preservation (T065)', () => {
let queue: ConnectionQueue;
beforeEach(() => {
queue = new ConnectionQueue({
maxSize: 10,
timeoutMs: 30000,
});
});
it('should_preserveFIFO_when_enqueueDequeue', async () => {
// Enqueue multiple requests
const requests = [
{ requestId: 'req-1', clientId: 'client-1', toolName: 'tool-1' },
{ requestId: 'req-2', clientId: 'client-2', toolName: 'tool-2' },
{ requestId: 'req-3', clientId: 'client-3', toolName: 'tool-3' },
];
for (const req of requests) {
await queue.enqueue(req);
}
// Dequeue and verify order
const dequeued = [];
for (let i = 0; i < 3; i++) {
const req = await queue.dequeue();
if (req) dequeued.push(req.requestId);
}
expect(dequeued).toEqual(['req-1', 'req-2', 'req-3']);
});
it('should_notReenqueue_with_eventPattern', async () => {
// With event-driven pattern, we don't re-enqueue
// Each request waits for its specific event
const emitter = new EventEmitter();
const processedOrder: string[] = [];
// Simulate 3 requests waiting for slots
const waiters = ['req-1', 'req-2', 'req-3'].map((requestId) => {
return new Promise<void>((resolve) => {
emitter.once(`slot-${requestId}`, () => {
processedOrder.push(requestId);
resolve();
});
});
});
// Emit events in FIFO order
emitter.emit('slot-req-1');
emitter.emit('slot-req-2');
emitter.emit('slot-req-3');
await Promise.all(waiters);
// Verify FIFO order maintained
expect(processedOrder).toEqual(['req-1', 'req-2', 'req-3']);
});
});
describe('Memory Leak Prevention (T068)', () => {
let emitter: EventEmitter;
beforeEach(() => {
emitter = new EventEmitter();
});
it('should_notAccumulateListeners_when_usingOnce', () => {
// .once() should auto-remove after firing
const requestId = 'test-mem-1';
emitter.once(`slot-${requestId}`, () => {});
expect(emitter.listenerCount(`slot-${requestId}`)).toBe(1);
emitter.emit(`slot-${requestId}`);
expect(emitter.listenerCount(`slot-${requestId}`)).toBe(0);
});
it('should_removeListeners_when_manualCleanup', () => {
// Manual .off() should remove listener
const requestId = 'test-mem-2';
const handler = () => {};
emitter.once(`slot-${requestId}`, handler);
expect(emitter.listenerCount(`slot-${requestId}`)).toBe(1);
emitter.off(`slot-${requestId}`, handler);
expect(emitter.listenerCount(`slot-${requestId}`)).toBe(0);
});
it('should_notLeakTimers_when_manyRequests', async () => {
// Verify setTimeout timers are cleaned up
const timers: NodeJS.Timeout[] = [];
for (let i = 0; i < 10; i++) {
const timeout = setTimeout(() => {}, 100);
timers.push(timeout);
}
// Clean up all timers
timers.forEach((t) => clearTimeout(t));
// No way to directly verify timers are cleared,
// but no memory leak should occur
expect(timers.length).toBe(10);
});
});
describe('Timeout Protection (T067)', () => {
let emitter: EventEmitter;
beforeEach(() => {
emitter = new EventEmitter();
});
it('should_timeout_when_eventNotFired', async () => {
const requestId = 'test-timeout-1';
const timeoutMs = 100;
const waitPromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
emitter.off(`slot-${requestId}`, handler);
reject(new Error(`Queue timeout: ${requestId} not processed within ${timeoutMs}ms`));
}, timeoutMs);
const handler = () => {
clearTimeout(timeout);
resolve('success');
};
emitter.once(`slot-${requestId}`, handler);
});
// Don't emit event
await expect(waitPromise).rejects.toThrow(/Queue timeout/);
});
it('should_useConfiguredTimeout_when_waiting', async () => {
const requestId = 'test-timeout-2';
const timeoutMs = 200;
const startTime = Date.now();
const waitPromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
emitter.off(`slot-${requestId}`, handler);
reject(new Error('Timeout'));
}, timeoutMs);
const handler = () => {
clearTimeout(timeout);
resolve('success');
};
emitter.once(`slot-${requestId}`, handler);
});
try {
await waitPromise;
} catch {
// Expected timeout
}
const elapsed = Date.now() - startTime;
// Should timeout around configured time (with some margin)
expect(elapsed).toBeGreaterThan(timeoutMs - 50);
expect(elapsed).toBeLessThan(timeoutMs + 100);
});
});
describe('High Concurrency Scenarios (T070)', () => {
let emitter: EventEmitter;
beforeEach(() => {
emitter = new EventEmitter();
// Set max listeners to prevent warnings (matching implementation)
emitter.setMaxListeners(200);
});
it('should_handleManyListeners_when_highConcurrency', async () => {
// Test with >10 concurrent requests (Node.js default EventEmitter warning threshold)
const numRequests = 50;
const promises: Promise<void>[] = [];
// Register 50 listeners (well above default threshold of 10)
for (let i = 0; i < numRequests; i++) {
const requestId = `req-${i}`;
const promise = new Promise<void>((resolve) => {
emitter.once(`slot-${requestId}`, () => resolve());
});
promises.push(promise);
}
// Verify all listeners registered
expect(emitter.eventNames().length).toBe(numRequests);
// Emit events for all requests
for (let i = 0; i < numRequests; i++) {
emitter.emit(`slot-req-${i}`);
}
// All promises should resolve without warnings or errors
await Promise.all(promises);
// Verify all listeners cleaned up (EventEmitter.once auto-removes)
expect(emitter.eventNames().length).toBe(0);
});
it('should_notExceedMaxListeners_when_queueFull', () => {
// Test that setMaxListeners prevents warnings with large queues
const maxListeners = emitter.getMaxListeners();
// Should be set to queue size (200 per implementation)
expect(maxListeners).toBe(200);
// Register listeners up to max without warnings
for (let i = 0; i < maxListeners; i++) {
emitter.once(`slot-req-${i}`, () => {});
}
// Verify no errors thrown
expect(emitter.eventNames().length).toBe(maxListeners);
});
});
});