veza/veza-backend-api/scripts/loadtest/k6_load_test.js
2025-12-16 11:23:49 -05:00

144 lines
4.7 KiB
JavaScript

// 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;
}