veza/loadtests/backend/stress_500rps.js
senke da837fc085
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
chore(release): v0.951 — Loadtest (500 req/s, 1000 WS, 50 uploads, perf indexes)
2026-03-02 19:22:38 +01:00

111 lines
4 KiB
JavaScript

/**
* Stress test: 500 req/s target on critical endpoints (v0.951)
* Endpoints: login, get tracks, search, marketplace products
* Usage: k6 run loadtests/backend/stress_500rps.js
* Output: k6 run --out json=report.json loadtests/backend/stress_500rps.js
*/
import http from 'k6/http';
import { check, sleep } from 'k6';
import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
const BASE_URL = __ENV.BASE_URL || __ENV.API_ORIGIN || 'http://localhost:8080';
const TEST_EMAIL_PREFIX = __ENV.TEST_EMAIL_PREFIX || 'user+stress';
const TEST_EMAIL_DOMAIN = __ENV.TEST_EMAIL_DOMAIN || 'example.com';
const TEST_PASSWORD_PREFIX = __ENV.TEST_PASSWORD_PREFIX || 'V3za!stress-';
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: `st${rand}`,
};
}
export const options = {
scenarios: {
stress: {
executor: 'ramping-vus',
stages: [
{ duration: '1m', target: 100 },
{ duration: '1m', target: 250 },
{ duration: '1m', target: 500 },
{ duration: '2m', target: 500 },
{ duration: '30s', target: 0 },
],
gracefulRampDown: '20s',
exec: 'stressScenario',
},
},
thresholds: {
'http_req_duration{name:login}': ['p(99)<500'],
'http_req_duration{name:tracks}': ['p(99)<500'],
'http_req_duration{name:search}': ['p(99)<500'],
'http_req_duration{name:products}': ['p(99)<500'],
http_req_duration: ['p(50)<100', 'p(95)<300', 'p(99)<500'],
http_req_failed: ['rate<0.05'],
},
};
export function setup() {
const users = [];
const baseURL = `${BASE_URL}/api/v1/auth`;
for (let i = 0; i < 100; i++) {
const user = generateTestUser();
const registerRes = http.post(`${baseURL}/register`, JSON.stringify(user), {
headers: { 'Content-Type': 'application/json' },
});
if (registerRes.status === 201) {
const loginRes = http.post(`${baseURL}/login`, JSON.stringify({ email: user.email, password: user.password }), {
headers: { 'Content-Type': 'application/json' },
});
if (loginRes.status === 200) {
try {
const body = JSON.parse(loginRes.body);
const token = body.data?.token?.access_token;
if (token) users.push({ ...user, token });
} catch (e) {}
}
}
}
return { users };
}
export function stressScenario(data) {
const { users } = data;
const baseURL = `${BASE_URL}/api/v1`;
const action = Math.floor(Math.random() * 4);
if (action === 0 && users.length > 0) {
const user = users[Math.floor(Math.random() * users.length)];
const res = http.post(
`${baseURL}/auth/login`,
JSON.stringify({ email: user.email, password: user.password }),
{
headers: { 'Content-Type': 'application/json' },
tags: { name: 'login' },
}
);
check(res, { 'login 200': (r) => r.status === 200 });
} else if (action === 1) {
const headers = users.length > 0
? { Authorization: `Bearer ${users[Math.floor(Math.random() * users.length)].token}` }
: {};
const res = http.get(`${baseURL}/tracks?limit=20`, { headers, tags: { name: 'tracks' } });
check(res, { 'tracks 200 or 401': (r) => r.status === 200 || r.status === 401 });
} else if (action === 2) {
const headers = users.length > 0
? { Authorization: `Bearer ${users[Math.floor(Math.random() * users.length)].token}` }
: {};
const q = ['test', 'music', 'beat', 'track', 'audio'][Math.floor(Math.random() * 5)];
const res = http.get(`${baseURL}/tracks/search?q=${q}&limit=10`, { headers, tags: { name: 'search' } });
check(res, { 'search 200 or 401': (r) => r.status === 200 || r.status === 401 });
} else {
const res = http.get(`${baseURL}/marketplace/products`, { tags: { name: 'products' } });
check(res, { 'products 200': (r) => r.status === 200 });
}
sleep(0.1);
}