/** * Performance Test Suite * Phase 3: Testing & Validation * * Measures response times, throughput, and identifies bottlenecks */ interface PerformanceMetric { endpoint: string; method: string; avgTime: number; minTime: number; maxTime: number; p95Time: number; p99Time: number; requestsPerSecond: number; } const BASE_URL = "http://localhost:5173"; const metrics: PerformanceMetric[] = []; // Helper to measure request time async function measureRequest( method: string, endpoint: string, body?: any, ): Promise { const start = performance.now(); try { const options: RequestInit = { method, headers: { "Content-Type": "application/json" }, }; if (body) options.body = JSON.stringify(body); await fetch(`${BASE_URL}${endpoint}`, options); return performance.now() - start; } catch { return performance.now() - start; } } // Run multiple requests and collect metrics async function benchmarkEndpoint( method: string, endpoint: string, numRequests: number = 50, body?: any, ): Promise { console.log( ` Benchmarking ${method.padEnd(6)} ${endpoint.padEnd(35)} (${numRequests} requests)...`, ); const times: number[] = []; const startTime = Date.now(); for (let i = 0; i < numRequests; i++) { const time = await measureRequest(method, endpoint, body); times.push(time); } const elapsed = Date.now() - startTime; times.sort((a, b) => a - b); const metric: PerformanceMetric = { endpoint, method, avgTime: times.reduce((a, b) => a + b) / times.length, minTime: times[0], maxTime: times[times.length - 1], p95Time: times[Math.floor(times.length * 0.95)], p99Time: times[Math.floor(times.length * 0.99)], requestsPerSecond: (numRequests / elapsed) * 1000, }; metrics.push(metric); return metric; } async function runPerformanceTests() { console.log("⚔ Performance Test Suite\n"); // CATEGORY 1: GET Endpoints (Read-Heavy) console.log("\nšŸ“Š Category 1: GET Endpoints (Read Performance)"); console.log("=".repeat(70)); console.log("\nBrowse endpoints (pagination-heavy):"); await benchmarkEndpoint("GET", "/api/creators?page=1&limit=20", 50); await benchmarkEndpoint("GET", "/api/opportunities?page=1&limit=20", 50); await benchmarkEndpoint("GET", "/api/applications?user_id=test-user", 40); console.log("\nFilter endpoints (filtered queries):"); await benchmarkEndpoint("GET", "/api/creators?arm=gameforge", 50); await benchmarkEndpoint( "GET", "/api/opportunities?arm=gameforge&sort=recent", 50, ); await benchmarkEndpoint("GET", "/api/creators?search=test", 40); console.log("\nIndividual resource retrieval:"); await benchmarkEndpoint("GET", "/api/creators/test_user", 50); await benchmarkEndpoint("GET", "/api/opportunities/test-opp-id", 40); await benchmarkEndpoint("GET", "/api/devconnect/link?user_id=test-user", 40); // CATEGORY 2: POST Endpoints (Write Performance) console.log("\nšŸ“Š Category 2: POST Endpoints (Write Performance)"); console.log("=".repeat(70)); const createCreatorBody = { user_id: `perf-test-${Date.now()}`, username: `perf_user_${Math.random().toString(36).substring(7)}`, bio: "Performance test creator", experience_level: "junior", }; console.log("\nCreate operations:"); await benchmarkEndpoint("POST", "/api/creators", 30, { ...createCreatorBody, user_id: `perf-${Date.now()}-1`, username: `perf_user_${Date.now()}_1`, }); const createOppBody = { user_id: `perf-creator-${Date.now()}`, title: "Performance Test Job", description: "Testing performance", job_type: "contract", }; await benchmarkEndpoint("POST", "/api/opportunities", 25, createOppBody); await benchmarkEndpoint("POST", "/api/applications", 20, { user_id: `perf-app-creator-${Date.now()}`, opportunity_id: "test-opp-id", cover_letter: "Performance test application", }); // CATEGORY 3: PUT Endpoints (Update Performance) console.log("\nšŸ“Š Category 3: PUT Endpoints (Update Performance)"); console.log("=".repeat(70)); console.log("\nUpdate operations:"); await benchmarkEndpoint("PUT", "/api/creators/test-id", 25, { bio: "Updated bio", skills: ["javascript", "typescript"], }); await benchmarkEndpoint("PUT", "/api/opportunities/test-opp-id", 20, { user_id: "test-user", title: "Updated Title", status: "closed", }); // CATEGORY 4: Complex Queries console.log("\nšŸ“Š Category 4: Complex & Heavy Queries"); console.log("=".repeat(70)); console.log("\nPaginated + Filtered queries:"); await benchmarkEndpoint( "GET", "/api/creators?arm=gameforge&search=test&page=1&limit=50", 30, ); await benchmarkEndpoint( "GET", "/api/opportunities?arm=labs&experienceLevel=senior&jobType=full-time&page=1&limit=50", 25, ); console.log("\nMulti-page traversal:"); await benchmarkEndpoint("GET", "/api/creators?page=2&limit=20", 30); await benchmarkEndpoint("GET", "/api/creators?page=5&limit=20", 30); await benchmarkEndpoint("GET", "/api/opportunities?page=3&limit=20", 25); // Generate Report console.log("\n" + "=".repeat(70)); console.log("\nšŸ“ˆ Performance Report\n"); // Group by endpoint const grouped = new Map(); metrics.forEach((m) => { const key = `${m.method} ${m.endpoint.split("?")[0]}`; if (!grouped.has(key)) grouped.set(key, []); grouped.get(key)!.push(m); }); // Print detailed metrics grouped.forEach((metricList, endpoint) => { const avg = metricList.reduce((sum, m) => sum + m.avgTime, 0) / metricList.length; const p95 = metricList.reduce((sum, m) => sum + m.p95Time, 0) / metricList.length; const p99 = metricList.reduce((sum, m) => sum + m.p99Time, 0) / metricList.length; const rps = metricList.reduce((sum, m) => sum + m.requestsPerSecond, 0) / metricList.length; console.log(`šŸ“ ${endpoint}`); console.log(` Avg: ${avg.toFixed(2)}ms`); console.log(` P95: ${p95.toFixed(2)}ms`); console.log(` P99: ${p99.toFixed(2)}ms`); console.log(` RPS: ${rps.toFixed(2)}`); console.log(""); }); // Performance targets console.log("šŸŽÆ Performance Targets & Compliance\n"); const targets = { "GET endpoints": { target: 100, actual: 0, threshold: "< 100ms" }, "POST endpoints": { target: 200, actual: 0, threshold: "< 200ms" }, "PUT endpoints": { target: 150, actual: 0, threshold: "< 150ms" }, "Complex queries": { target: 250, actual: 0, threshold: "< 250ms" }, }; let targetsPassed = 0; let targetsFailed = 0; metrics.forEach((m) => { if (m.method === "GET") { targets["GET endpoints"].actual = Math.max( targets["GET endpoints"].actual, m.avgTime, ); } else if (m.method === "POST") { targets["POST endpoints"].actual = Math.max( targets["POST endpoints"].actual, m.avgTime, ); } else if (m.method === "PUT") { targets["PUT endpoints"].actual = Math.max( targets["PUT endpoints"].actual, m.avgTime, ); } }); // Check complex queries const complexQueries = metrics.filter( (m) => m.endpoint.includes("?") && m.endpoint.split("&").length > 2, ); if (complexQueries.length > 0) { targets["Complex queries"].actual = Math.max( ...complexQueries.map((m) => m.avgTime), ); } Object.entries(targets).forEach(([category, data]) => { if (data.actual === 0) return; // Skip if no data const passed = data.actual <= data.target; const symbol = passed ? "āœ“" : "āœ—"; console.log( `${symbol} ${category.padEnd(20)} ${data.actual.toFixed(2)}ms ${data.threshold}`, ); if (passed) targetsPassed++; else targetsFailed++; }); // Summary Statistics console.log("\n" + "=".repeat(70)); console.log("\nšŸ“Š Summary Statistics\n"); const allTimes = metrics.map((m) => m.avgTime); const slowestEndpoint = metrics.reduce((a, b) => a.avgTime > b.avgTime ? a : b, ); const fastestEndpoint = metrics.reduce((a, b) => a.avgTime < b.avgTime ? a : b, ); console.log(`Total Endpoints Tested: ${metrics.length}`); console.log( `Average Response Time: ${(allTimes.reduce((a, b) => a + b) / allTimes.length).toFixed(2)}ms`, ); console.log( `Fastest: ${fastestEndpoint.method} ${fastestEndpoint.endpoint.split("?")[0]} (${fastestEndpoint.avgTime.toFixed(2)}ms)`, ); console.log( `Slowest: ${slowestEndpoint.method} ${slowestEndpoint.endpoint.split("?")[0]} (${slowestEndpoint.avgTime.toFixed(2)}ms)`, ); console.log( `\nPerformance Targets: ${targetsPassed} passed, ${targetsFailed} failed`, ); if (targetsFailed === 0) { console.log("\nāœ… All performance targets met!"); } else { console.log( `\nāš ļø ${targetsFailed} performance targets not met. Optimization needed.`, ); } console.log("\n" + "=".repeat(70)); return { passed: targetsPassed, failed: targetsFailed }; } runPerformanceTests() .then((summary) => { process.exit(summary.failed > 0 ? 1 : 0); }) .catch((error) => { console.error("Performance test failed:", error); process.exit(1); });