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