144 lines
4.7 KiB
JavaScript
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;
|
|
}
|