166 lines
4.6 KiB
JavaScript
166 lines
4.6 KiB
JavaScript
|
|
/**
|
||
|
|
* 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);
|
||
|
|
}
|