/** * API Performance Load Test * * Tests specific API endpoints for performance * Tests: * - REST API endpoints (Pages, Posts, Portfolio, Categories) * - GraphQL API queries * - Global API endpoint * - Authentication endpoints * * NFR4 Requirements: * - p95 response time < 300ms (faster for API) * - Error rate < 0.5% (stricter for API) * - Throughput > 100 requests/second */ import http from 'k6/http'; import { check, group } from 'k6'; import { urls, thresholdGroups, config } from './lib/config.js'; import { ApiHelper, testData, thinkTime } from './lib/helpers.js'; // Test configuration export const options = { stages: config.stages.apiPerformance, thresholds: thresholdGroups.api, ...config.requestOptions, }; // API helper (no auth needed for public endpoints) const apiHelper = new ApiHelper(config.baseUrl); /** * Setup function */ export function setup() { console.log('=== API Performance Load Test ==='); console.log(`Target: ${config.baseUrl}`); console.log('Starting test...'); } /** * Main test scenario */ export default function () { // Scenario 1: Global API endpoint (metadata) group('Global API', () => { const res = apiHelper.get('/global'); check(res, { 'has global data': (r) => { try { const body = r.json(); return body !== null; } catch { return false; } }, }); thinkTime(0.1, 0.3); // Minimal think time for API }); // Scenario 2: Pages API group('Pages API', () => { // List pages apiHelper.get('/pages', { limit: 10, depth: 1 }); // Try to get a specific page try { const list = apiHelper.get('/pages', { limit: 1, depth: 0, page: 1 }); if (list.status === 200 && list.json('totalDocs') > 0) { const firstId = list.json('docs')[0].id; apiHelper.get(`/pages/${firstId}`, { depth: 1 }); } } catch (e) { // Page might not exist } thinkTime(0.1, 0.3); }); // Scenario 3: Posts API group('Posts API', () => { // List posts apiHelper.get('/posts', { limit: 10, depth: 1 }); // List with pagination apiHelper.get('/posts', { limit: 20, depth: 0, page: 1 }); thinkTime(0.1, 0.3); }); // Scenario 4: Portfolio API group('Portfolio API', () => { // List portfolio items apiHelper.get('/portfolio', { limit: 10, depth: 1 }); // Filter by category (if applicable) try { const categories = apiHelper.get('/categories', { limit: 1, depth: 0 }); if (categories.status === 200 && categories.json('totalDocs') > 0) { const categoryId = categories.json('docs')[0].id; apiHelper.get('/portfolio', { limit: 10, depth: 1, where: { category: { equals: categoryId }, }, }); } } catch (e) { // Categories might not exist } thinkTime(0.1, 0.3); }); // Scenario 5: Categories API group('Categories API', () => { // List all categories apiHelper.get('/categories', { limit: 10, depth: 1 }); thinkTime(0.1, 0.3); }); // Scenario 6: GraphQL API group('GraphQL API', () => { // Query 1: Simple list const simpleQuery = ` query { Posts(limit: 5) { docs { id title slug } } } `; apiHelper.graphql(simpleQuery); thinkTime(0.1, 0.2); // Query 2: With relationships const complexQuery = ` query { Posts(limit: 3, depth: 2) { docs { id title content category { id name } } } } `; apiHelper.graphql(complexQuery); thinkTime(0.1, 0.3); }); // Scenario 7: Authentication endpoints group('Auth API', () => { // Note: These will fail with invalid credentials, but test the endpoint response const loginRes = http.post(`${config.baseUrl}/api/users/login`, JSON.stringify({ email: 'test@example.com', password: 'wrongpassword', }), { headers: { 'Content-Type': 'application/json' }, }); check(loginRes, { 'login responds': (r) => [200, 400, 401].includes(r.status), 'login responds quickly': (r) => r.timings.duration < 500, }); thinkTime(0.1, 0.2); }); // Scenario 8: Concurrent API requests group('Concurrent Requests', () => { // Simulate multiple API calls in parallel const requests = [ apiHelper.get('/pages', { limit: 5, depth: 1 }), apiHelper.get('/posts', { limit: 5, depth: 1 }), apiHelper.get('/portfolio', { limit: 5, depth: 1 }), ]; // Check all succeeded const allSuccessful = requests.every(r => r.status === 200); check(null, { 'all concurrent requests successful': () => allSuccessful, }); thinkTime(0.2, 0.5); }); // Scenario 9: Filtered queries group('Filtered Queries', () => { // Various filter combinations const filters = [ { where: { status: { equals: 'published' } } }, { limit: 5, sort: '-createdAt' }, { limit: 10, depth: 2 }, ]; filters.forEach((filter, i) => { apiHelper.get('/posts', filter); thinkTime(0.05, 0.15); }); }); // Minimal think time for API-focused test thinkTime(0.5, 1); } /** * Teardown function */ export function teardown(data) { console.log('=== API Performance Test Complete ==='); console.log('Check results above for:'); console.log('- p95 response time < 300ms'); console.log('- Error rate < 0.5%'); console.log('- Throughput > 100 req/s'); }