fix(cache): prevent data loss on 5xx and validate cookies
Critical fixes: 1. getActivityDetailsRaw never throws on 5xx - returns null immediately 2. Cache-manager preserves existing data when fetch returns null 3. After 5xx error, validate cookie on next request (backend may invalidate sessions) 4. Cookie validation: fetch activity ID 1 to test, re-login if fails This prevents local cache corruption during server outages.
This commit is contained in:
141
test/test-concurrency.ts
Normal file
141
test/test-concurrency.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
// test/test-concurrency.ts
|
||||
/**
|
||||
* Test script for concurrency features
|
||||
* Run with: bun run test/test-concurrency.ts
|
||||
*/
|
||||
|
||||
import { Semaphore, executeWithConcurrency, BatchProcessor } from '../utils/semaphore';
|
||||
|
||||
// Simulate API call
|
||||
function simulateApiCall(id: number, delay: number = 100): Promise<{ id: number; result: string }> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({ id, result: `Result for ${id}` });
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
async function testSemaphore(): Promise<void> {
|
||||
console.log('\n=== Test 1: Basic Semaphore ===');
|
||||
const semaphore = new Semaphore(3);
|
||||
|
||||
const start = Date.now();
|
||||
const promises = [];
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const id = i;
|
||||
promises.push(
|
||||
(async () => {
|
||||
await semaphore.acquire();
|
||||
console.log(`[${id}] Acquired permit (available: ${semaphore.getAvailablePermits()})`);
|
||||
await simulateApiCall(id, 200);
|
||||
console.log(`[${id}] Releasing permit`);
|
||||
semaphore.release();
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
const duration = Date.now() - start;
|
||||
console.log(`\n✓ Completed 10 tasks with max 3 concurrent in ${duration}ms`);
|
||||
console.log(` (Sequential would take ~2000ms, parallel should be ~700-800ms)`);
|
||||
}
|
||||
|
||||
async function testExecuteWithConcurrency(): Promise<void> {
|
||||
console.log('\n=== Test 2: executeWithConcurrency ===');
|
||||
|
||||
const tasks = Array.from({ length: 10 }, (_, i) => () => simulateApiCall(i + 1, 100));
|
||||
|
||||
const start = Date.now();
|
||||
const results = await executeWithConcurrency(tasks, 5);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
console.log(`✓ Completed ${results.length} tasks with max 5 concurrent in ${duration}ms`);
|
||||
console.log(` Results: ${results.map(r => r.id).join(', ')}`);
|
||||
}
|
||||
|
||||
async function testBatchProcessor(): Promise<void> {
|
||||
console.log('\n=== Test 3: BatchProcessor ===');
|
||||
|
||||
const items = Array.from({ length: 20 }, (_, i) => ({ id: i + 1, name: `Item ${i + 1}` }));
|
||||
let processedCount = 0;
|
||||
|
||||
const processor = new BatchProcessor(
|
||||
async (item: { id: number; name: string }) => {
|
||||
await simulateApiCall(item.id, 50);
|
||||
processedCount++;
|
||||
return { ...item, processed: true };
|
||||
},
|
||||
4, // concurrency
|
||||
{
|
||||
onProgress: (completed, total) => {
|
||||
if (completed % 5 === 0 || completed === total) {
|
||||
console.log(` Progress: ${completed}/${total} (${Math.round(completed / total * 100)}%)`);
|
||||
}
|
||||
},
|
||||
onError: (error, item) => {
|
||||
console.error(` Error processing item ${item.id}:`, error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const start = Date.now();
|
||||
const results = await processor.process(items);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
console.log(`✓ Processed ${results.length} items with max 4 concurrent in ${duration}ms`);
|
||||
console.log(` Expected ~500ms (20 items / 4 concurrent * 50ms each)`);
|
||||
}
|
||||
|
||||
async function testErrorHandling(): Promise<void> {
|
||||
console.log('\n=== Test 4: Error Handling ===');
|
||||
|
||||
const items = [1, 2, 3, 4, 5];
|
||||
let errorCount = 0;
|
||||
|
||||
const processor = new BatchProcessor(
|
||||
async (id: number) => {
|
||||
if (id % 2 === 0) {
|
||||
throw new Error(`Simulated error for ${id}`);
|
||||
}
|
||||
return { id, success: true };
|
||||
},
|
||||
3,
|
||||
{
|
||||
onError: (error, item) => {
|
||||
errorCount++;
|
||||
console.log(` Caught error for item ${item}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const results = await processor.process(items);
|
||||
console.log(`✓ Completed: ${results.length} success, ${errorCount} errors (errors handled gracefully)`);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
console.log('╔═══════════════════════════════════════════════════╗');
|
||||
console.log('║ Concurrency Module Test Suite ║');
|
||||
console.log('╚═══════════════════════════════════════════════════╝');
|
||||
|
||||
try {
|
||||
await testSemaphore();
|
||||
await testExecuteWithConcurrency();
|
||||
await testBatchProcessor();
|
||||
await testErrorHandling();
|
||||
|
||||
console.log('\n╔═══════════════════════════════════════════════════╗');
|
||||
console.log('║ All tests passed! ✓ ║');
|
||||
console.log('╚═══════════════════════════════════════════════════╝');
|
||||
console.log('\n📝 Configuration:');
|
||||
console.log(' - Set CONCURRENT_API_CALLS in .env to control parallelism');
|
||||
console.log(' - Current default: 8 concurrent requests');
|
||||
console.log(' - Example: CONCURRENT_API_CALLS=16 for faster crawling');
|
||||
console.log('');
|
||||
} catch (error) {
|
||||
console.error('\n❌ Test failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user