/** * Integration Tests: FIFO Queue Concurrency * * Test T029: Verify concurrent requests are processed in FIFO order (one at a time) * Tests the request queue implementation for /sitemap.xml endpoint * * @module tests/integration/queue-concurrency */ import { describe, it, before, after } from 'node:test'; import assert from 'node:assert/strict'; import http from 'node:http'; const TEST_PORT = 3001; // ============================================================================= // T029: Integration test for concurrent requests (FIFO processing) // ============================================================================= describe('T029: Concurrent Requests FIFO Processing', () => { it('should process multiple concurrent requests in FIFO order (sequential)', async () => { // Send multiple requests simultaneously const requestCount = 5; const startTime = Date.now(); const requests = []; // Launch all requests at once for (let i = 0; i < requestCount; i++) { requests.push(makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, i)); } // Wait for all requests to complete const responses = await Promise.all(requests); // Verify all requests succeeded responses.forEach((response, index) => { assert.equal( response.statusCode, 200, `Request ${index} should succeed with 200 OK` ); }); // Verify sequential processing (FIFO) // Each request should complete before the next starts // If processed in parallel, total time ≈ single request time // If processed sequentially, total time ≈ single request time × count const totalElapsed = Date.now() - startTime; const averageRequestTime = responses.reduce((sum, r) => sum + r.elapsed, 0) / responses.length; // Sequential processing means total time should be close to sum of individual times // Allow some overhead for queue management const expectedMinTime = averageRequestTime * (requestCount - 1); // Allow first request to be instant assert.ok( totalElapsed >= expectedMinTime * 0.8, // 80% threshold for timing variability `Total time (${totalElapsed}ms) should be close to sequential sum (${expectedMinTime}ms), indicating FIFO processing` ); }); it('should maintain FIFO order: first request finishes before second starts processing', async () => { // Track request processing order const processingLog = []; // Mock Drive API to log when each request is processed // TODO: Add timing hooks in implementation to verify order // Send two requests with small delay const request1 = makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 1); // Small delay to ensure request 1 is queued first await new Promise(resolve => setTimeout(resolve, 10)); const request2 = makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 2); const [response1, response2] = await Promise.all([request1, request2]); // Both should succeed assert.equal(response1.statusCode, 200, 'Request 1 should succeed'); assert.equal(response2.statusCode, 200, 'Request 2 should succeed'); // Request 1 should complete before request 2 starts processing // Verify by checking that request 2 completion time > request 1 completion time assert.ok( response2.completedAt > response1.completedAt, 'Request 2 should complete after Request 1 (FIFO order)' ); }); it('should only process one request at a time (no concurrent Drive API calls)', async () => { // Send 3 requests simultaneously const requests = [ makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 1), makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 2), makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 3) ]; const responses = await Promise.all(requests); // Verify all succeeded responses.forEach((response, index) => { assert.equal(response.statusCode, 200, `Request ${index + 1} should succeed`); }); // Check that completion times don't overlap // Sort responses by completion time const sortedResponses = responses.sort((a, b) => a.completedAt - b.completedAt); // Each request should complete before the next one starts for (let i = 0; i < sortedResponses.length - 1; i++) { const current = sortedResponses[i]; const next = sortedResponses[i + 1]; // Next request should start after current completes // (Allow small timing variance) assert.ok( next.startedAt >= current.completedAt - 50, // 50ms tolerance for timing `Request ${i + 2} should start after Request ${i + 1} completes (FIFO guarantee)` ); } }); it('should handle queue correctly when requests fail', async () => { // Test scenario: Request 1 succeeds, Request 2 fails (e.g., Drive API error), Request 3 succeeds // Queue should continue processing despite failures // TODO: Mock Drive API to fail for specific request const requests = [ makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 1), // Should succeed makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 2), // Will fail (mock) makeTimedRequest(`http://localhost:${TEST_PORT}/sitemap.xml`, 3) // Should succeed ]; const responses = await Promise.all(requests); // Request 1 should succeed assert.equal(responses[0].statusCode, 200, 'Request 1 should succeed'); // Request 2 should fail (mocked error) // assert.notEqual(responses[1].statusCode, 200, 'Request 2 should fail'); // Request 3 should still succeed (queue continues) assert.equal(responses[2].statusCode, 200, 'Request 3 should succeed despite Request 2 failure'); // All requests should still be processed in FIFO order assert.ok( responses[0].completedAt < responses[1].completedAt, 'Request 1 should complete before Request 2' ); assert.ok( responses[1].completedAt < responses[2].completedAt, 'Request 2 should complete before Request 3' ); }); }); // ============================================================================= // Helper Functions // ============================================================================= /** * Make HTTP request and track timing * @param {string} url - Full URL to request * @param {number} requestId - Request identifier for logging * @returns {Promise} Response with timing data */ function makeTimedRequest(url, requestId) { const startedAt = Date.now(); return new Promise((resolve, reject) => { http.get(url, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { const completedAt = Date.now(); const elapsed = completedAt - startedAt; resolve({ requestId, statusCode: res.statusCode, headers: res.headers, body, startedAt, completedAt, elapsed }); }); }).on('error', reject); }); }