318 lines
10 KiB
JavaScript
318 lines
10 KiB
JavaScript
/**
|
|
* 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<void>}
|
|
*/
|
|
function delay(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|