// k6 load test pour veza-backend-api // Installation: https://k6.io/docs/getting-started/installation/ // Usage: k6 run scripts/loadtest/k6_load_test.js import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend } from 'k6/metrics'; // Métriques custom const errorRate = new Rate('errors'); const healthCheckDuration = new Trend('health_check_duration'); const readyzCheckDuration = new Trend('readyz_check_duration'); // Configuration export const options = { stages: [ { duration: '30s', target: 10 }, // Ramp-up: 0 à 10 VUs en 30s { duration: '1m', target: 10 }, // Stabilité: 10 VUs pendant 1m { duration: '30s', target: 0 }, // Ramp-down: 10 à 0 VUs en 30s ], thresholds: { 'http_req_duration': ['p(95)<500', 'p(99)<1000'], // 95% < 500ms, 99% < 1s 'errors': ['rate<0.05'], // < 5% d'erreurs 'health_check_duration': ['p(95)<100'], // Health check < 100ms 'readyz_check_duration': ['p(95)<200'], // Readyz check < 200ms }, }; // Base URL (configurable via env) const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080'; // Token pour endpoints authentifiés (optionnel, pour tests auth) const AUTH_TOKEN = __ENV.AUTH_TOKEN || ''; export default function () { // 1. Health check (/health) const healthRes = http.get(`${BASE_URL}/health`); const healthCheck = check(healthRes, { 'health status is 200': (r) => r.status === 200, 'health response has status': (r) => { try { const body = JSON.parse(r.body); return body.success === true && body.data && body.data.status; } catch (e) { return false; } }, }); errorRate.add(!healthCheck); healthCheckDuration.add(healthRes.timings.duration); sleep(0.5); // 2. Readiness check (/readyz) const readyzRes = http.get(`${BASE_URL}/readyz`); const readyzCheck = check(readyzRes, { 'readyz status is 200': (r) => r.status === 200, 'readyz response has status': (r) => { try { const body = JSON.parse(r.body); return body.success === true && body.data && body.data.status; } catch (e) { return false; } }, }); errorRate.add(!readyzCheck); readyzCheckDuration.add(readyzRes.timings.duration); sleep(0.5); // 3. Auth endpoint (POST /api/v1/auth/login) - test avec credentials invalides (attendu: 401) const loginPayload = JSON.stringify({ email: 'test@example.com', password: 'invalid_password', }); const loginRes = http.post(`${BASE_URL}/api/v1/auth/login`, loginPayload, { headers: { 'Content-Type': 'application/json' }, }); const loginCheck = check(loginRes, { 'login returns 401 or 400': (r) => r.status === 401 || r.status === 400, 'login error format is correct': (r) => { try { const body = JSON.parse(r.body); return body.success === false && body.error; } catch (e) { return false; } }, }); errorRate.add(!loginCheck); sleep(1); // 4. Track list endpoint (GET /api/v1/tracks) - sans auth (attendu: 401 ou 200 selon config) const tracksRes = http.get(`${BASE_URL}/api/v1/tracks`, { headers: AUTH_TOKEN ? { 'Authorization': `Bearer ${AUTH_TOKEN}` } : {}, }); const tracksCheck = check(tracksRes, { 'tracks returns 200 or 401': (r) => r.status === 200 || r.status === 401, }); errorRate.add(tracksRes.status >= 500); // Seulement erreurs 5xx comptent comme erreurs sleep(1); } export function handleSummary(data) { return { 'stdout': textSummary(data, { indent: ' ', enableColors: true }), 'scripts/loadtest/k6_summary.json': JSON.stringify(data), }; } function textSummary(data, options) { const indent = options.indent || ''; const enableColors = options.enableColors || false; let summary = '\n'; summary += `${indent}Load Test Summary\n`; summary += `${indent}==================\n\n`; // HTTP Requests summary += `${indent}HTTP Requests:\n`; summary += `${indent} Total: ${data.metrics.http_reqs.values.count}\n`; summary += `${indent} Failed: ${data.metrics.http_req_failed.values.rate * 100}%\n\n`; // Durations summary += `${indent}Durations:\n`; summary += `${indent} P95: ${data.metrics.http_req_duration.values['p(95)']}ms\n`; summary += `${indent} P99: ${data.metrics.http_req_duration.values['p(99)']}ms\n\n`; // Health checks if (data.metrics.health_check_duration) { summary += `${indent}Health Check:\n`; summary += `${indent} P95: ${data.metrics.health_check_duration.values['p(95)']}ms\n`; } if (data.metrics.readyz_check_duration) { summary += `${indent}Readyz Check:\n`; summary += `${indent} P95: ${data.metrics.readyz_check_duration.values['p(95)']}ms\n`; } return summary; }