Files
google-drive-content-adapter/tests/integration/queue-concurrency.test.js

193 lines
7.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<Object>} 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);
});
}