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:
JamesFlare1212
2026-04-10 23:31:40 -04:00
parent 5fb60b069f
commit c447dc51ee
4 changed files with 454 additions and 67 deletions

141
test/test-concurrency.ts Normal file
View 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();