/** * Load test: register, login, /me, refresh * Chemins corrigés: /api/v1/auth/* * Usage: k6 run loadtests/backend/auth.js */ import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend } from 'k6/metrics'; import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; const API_ORIGIN = __ENV.API_ORIGIN || __ENV.BASE_URL || 'http://localhost:8080'; const TEST_EMAIL_PREFIX = __ENV.TEST_EMAIL_PREFIX || 'user+load'; const TEST_EMAIL_DOMAIN = __ENV.TEST_EMAIL_DOMAIN || 'example.com'; const TEST_PASSWORD_PREFIX = __ENV.TEST_PASSWORD_PREFIX || 'V3za!load-'; const loginDuration = new Trend('login_duration'); const loginFailureRate = new Rate('login_failures'); const registerDuration = new Trend('register_duration'); const registerFailureRate = new Rate('register_failures'); export const options = { scenarios: { auth_flow: { executor: 'ramping-vus', stages: [ { duration: '30s', target: 5 }, { duration: '1m', target: 10 }, { duration: '30s', target: 0 }, ], gracefulRampDown: '30s', }, }, thresholds: { http_req_duration: ['p(95)<300', 'p(99)<500'], login_duration: ['p(95)<300', 'p(99)<500'], register_duration: ['p(95)<800', 'p(99)<1000'], login_failures: ['rate<0.1'], register_failures: ['rate<0.1'], http_req_failed: ['rate<0.1'], }, }; function generateTestUser() { const rand = randomString(8); const pwd = `${TEST_PASSWORD_PREFIX}${rand}`; return { email: `${TEST_EMAIL_PREFIX}${rand}@${TEST_EMAIL_DOMAIN}`, password: pwd, password_confirmation: pwd, username: `user${rand}`, }; } export function setup() { const users = []; const baseURL = `${API_ORIGIN}/api/v1/auth`; for (let i = 0; i < 20; i++) { const user = generateTestUser(); const registerRes = http.post(`${baseURL}/register`, JSON.stringify(user), { headers: { 'Content-Type': 'application/json' }, }); if (registerRes.status === 201) { users.push(user); } } return { users }; } export default function (data) { const { users } = data; if (users.length === 0) return; const user = users[Math.floor(Math.random() * users.length)]; const baseURL = `${API_ORIGIN}/api/v1/auth`; const loginPayload = JSON.stringify({ email: user.email, password: user.password, }); const loginStart = Date.now(); const loginRes = http.post(`${baseURL}/login`, loginPayload, { headers: { 'Content-Type': 'application/json' }, }); loginDuration.add(Date.now() - loginStart); loginFailureRate.add(loginRes.status !== 200); check(loginRes, { 'login status is 200': (r) => r.status === 200, 'login has access_token': (r) => { try { const body = JSON.parse(r.body); return body.success && body.data?.token?.access_token; } catch { return false; } }, }); let accessToken = ''; if (loginRes.status === 200) { try { const body = JSON.parse(loginRes.body); accessToken = body.data?.token?.access_token || ''; } catch (e) {} } sleep(1); if (accessToken) { const meRes = http.get(`${baseURL}/me`, { headers: { Authorization: `Bearer ${accessToken}` }, }); check(meRes, { 'profile status is 200': (r) => r.status === 200, 'profile has user data': (r) => { try { const body = JSON.parse(r.body); return body.success && body.data?.id && body.data?.email; } catch { return false; } }, }); } sleep(1); if (Math.random() < 0.1) { const invalidRes = http.post( `${baseURL}/login`, JSON.stringify({ email: user.email, password: 'WrongPassword123!' }), { headers: { 'Content-Type': 'application/json' } } ); check(invalidRes, { 'invalid login returns 401': (r) => r.status === 401 }); } sleep(1); if (loginRes.status === 200) { try { const body = JSON.parse(loginRes.body); const refreshToken = body.data?.token?.refresh_token; if (refreshToken) { const refreshRes = http.post( `${baseURL}/refresh`, JSON.stringify({ refresh_token: refreshToken }), { headers: { 'Content-Type': 'application/json' } } ); check(refreshRes, { 'refresh status is 200': (r) => r.status === 200, 'refresh has new tokens': (r) => { try { const rb = JSON.parse(r.body); return rb.success && rb.data?.access_token; } catch { return false; } }, }); } } catch (e) {} } sleep(2); }