/** * Unit Tests: FIFO Request Queue * * Tests T038-T039: Test FIFO queue implementation * Tests the queue.js module in isolation * * @module tests/unit/queue */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; // ============================================================================= // T038: Unit test for FIFO queue enqueue/dequeue // ============================================================================= describe('T038: FIFO Queue Enqueue/Dequeue', () => { it('should enqueue and dequeue requests in FIFO order', async () => { // TODO: Import RequestQueue from src/queue.js // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); const results = []; // Enqueue 3 tasks const task1 = async () => { await delay(10); results.push('task1'); return 'result1'; }; const task2 = async () => { await delay(10); results.push('task2'); return 'result2'; }; const task3 = async () => { await delay(10); results.push('task3'); return 'result3'; }; // Enqueue all tasks // const promise1 = queue.enqueue(task1); // const promise2 = queue.enqueue(task2); // const promise3 = queue.enqueue(task3); // Wait for all to complete // await Promise.all([promise1, promise2, promise3]); // Verify FIFO order // assert.deepEqual(results, ['task1', 'task2', 'task3'], 'Tasks should complete in FIFO order'); }); it('should process tasks sequentially (one at a time)', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); let activeTaskCount = 0; let maxActiveTaskCount = 0; const createTask = (id) => async () => { activeTaskCount++; maxActiveTaskCount = Math.max(maxActiveTaskCount, activeTaskCount); await delay(50); activeTaskCount--; return `task${id}`; }; // Enqueue multiple tasks const promises = []; for (let i = 1; i <= 5; i++) { // promises.push(queue.enqueue(createTask(i))); } // await Promise.all(promises); // Verify only one task was active at a time // assert.equal(maxActiveTaskCount, 1, 'Only one task should be active at a time'); }); it('should maintain queue order when tasks are added during processing', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); const results = []; // Add initial task // queue.enqueue(async () => { // await delay(20); // results.push('task1'); // }); // Add second task after slight delay // await delay(5); // queue.enqueue(async () => { // await delay(10); // results.push('task2'); // }); // Add third task after slight delay // await delay(5); // queue.enqueue(async () => { // await delay(10); // results.push('task3'); // }); // Wait for all tasks to complete // await delay(100); // Verify order preserved // assert.deepEqual(results, ['task1', 'task2', 'task3'], 'Should maintain FIFO order even when tasks added during processing'); }); it('should return task result through promise', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); const task = async () => { return 'test-result'; }; // const result = await queue.enqueue(task); // assert.equal(result, 'test-result', 'Should return task result through promise'); }); it('should propagate task errors through promise', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); const task = async () => { throw new Error('Task failed'); }; // await assert.rejects( // async () => await queue.enqueue(task), // { message: 'Task failed' }, // 'Should propagate task error' // ); }); }); // ============================================================================= // T039: Unit test for FIFO queue concurrent request handling // ============================================================================= describe('T039: FIFO Queue Concurrent Request Handling', () => { it('should use processing flag to prevent simultaneous execution', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); let processingCheckpoints = []; const createTask = (id) => async () => { // Log when task starts processingCheckpoints.push({ id, event: 'start', time: Date.now() }); await delay(30); // Log when task ends processingCheckpoints.push({ id, event: 'end', time: Date.now() }); return id; }; // Enqueue 3 tasks simultaneously const promises = [ // queue.enqueue(createTask(1)), // queue.enqueue(createTask(2)), // queue.enqueue(createTask(3)) ]; // await Promise.all(promises); // Verify processing flag prevented overlap // Check that task N ends before task N+1 starts // const task1End = processingCheckpoints.find(cp => cp.id === 1 && cp.event === 'end'); // const task2Start = processingCheckpoints.find(cp => cp.id === 2 && cp.event === 'start'); // const task2End = processingCheckpoints.find(cp => cp.id === 2 && cp.event === 'end'); // const task3Start = processingCheckpoints.find(cp => cp.id === 3 && cp.event === 'start'); // assert.ok(task1End.time <= task2Start.time, 'Task 2 should start after Task 1 ends'); // assert.ok(task2End.time <= task3Start.time, 'Task 3 should start after Task 2 ends'); }); it('should clear processing flag after task completes', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); // Add task // await queue.enqueue(async () => { // await delay(10); // return 'done'; // }); // Verify processing flag is cleared (queue can accept new tasks) // assert.equal(queue.isProcessing(), false, 'Processing flag should be cleared after task completes'); }); it('should clear processing flag even if task throws error', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); // Add task that throws error try { // await queue.enqueue(async () => { // await delay(10); // throw new Error('Task failed'); // }); } catch (e) { // Expected error } // Verify processing flag is cleared (queue can accept new tasks) // assert.equal(queue.isProcessing(), false, 'Processing flag should be cleared even after task error'); // Verify next task can be processed // const result = await queue.enqueue(async () => 'next-task'); // assert.equal(result, 'next-task', 'Next task should process successfully after error'); }); it('should handle empty queue correctly (no processing when queue empty)', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); // Verify processing flag is false for empty queue // assert.equal(queue.isProcessing(), false, 'Processing flag should be false for empty queue'); // assert.equal(queue.getQueueLength(), 0, 'Queue should be empty'); }); it('should use EventEmitter for queue management', async () => { // Per task spec: "Implement FIFO request queue class in src/queue.js using Node.js EventEmitter" // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); // Verify queue extends or uses EventEmitter // assert.ok(queue.on, 'Queue should have EventEmitter methods'); // assert.ok(queue.emit, 'Queue should have emit method'); }); it('should maintain queue array for pending tasks', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); // Add tasks without waiting // queue.enqueue(async () => { // await delay(50); // return 'task1'; // }); // queue.enqueue(async () => 'task2'); // queue.enqueue(async () => 'task3'); // Check queue length while first task is processing // await delay(10); // Let first task start processing // Queue should have 2 pending tasks (task2 and task3) // Note: task1 is being processed, not in queue // assert.ok(queue.getQueueLength() >= 2, 'Queue should contain pending tasks'); }); it('should process queue in correct order after processing flag is cleared', async () => { // TODO: Import RequestQueue // const { RequestQueue } = await import('../../src/queue.js'); // const queue = new RequestQueue(); const results = []; // Add first task (starts processing immediately) // queue.enqueue(async () => { // await delay(30); // results.push('task1'); // }); // Add more tasks while first is processing // await delay(5); // queue.enqueue(async () => { // results.push('task2'); // }); // queue.enqueue(async () => { // results.push('task3'); // }); // Wait for all to complete // await delay(100); // Verify FIFO order maintained // assert.deepEqual(results, ['task1', 'task2', 'task3'], 'Should process in FIFO order after processing flag cleared'); }); }); // ============================================================================= // Helper Functions // ============================================================================= /** * Delay helper for async tests * @param {number} ms - Milliseconds to delay * @returns {Promise} */ function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }