/** * Load test: GET playlists, GET playlists/:id, POST playlists, search * Usage: k6 run loadtests/backend/playlists.js * Requires: setup creates auth users */ import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate } from 'k6/metrics'; import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; const errorRate = new Rate('errors'); const API_ORIGIN = __ENV.API_ORIGIN || __ENV.BASE_URL || 'http://localhost:8080'; const TEST_EMAIL_PREFIX = __ENV.TEST_EMAIL_PREFIX || 'user+playlist'; const TEST_EMAIL_DOMAIN = __ENV.TEST_EMAIL_DOMAIN || 'example.com'; const TEST_PASSWORD_PREFIX = __ENV.TEST_PASSWORD_PREFIX || 'V3za!playlist-'; 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: `pl${rand}`, }; } export const options = { scenarios: { playlists: { executor: 'ramping-vus', stages: [ { duration: '30s', target: 5 }, { duration: '1m', target: 5 }, { duration: '30s', target: 0 }, ], }, }, thresholds: { http_req_duration: ['p(95)<500', 'p(99)<1000'], errors: ['rate<0.1'], }, }; export function setup() { const users = []; const baseURL = `${API_ORIGIN}/api/v1/auth`; for (let i = 0; i < 15; 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 default function (data) { const { users } = data; if (users.length === 0) return; const user = users[Math.floor(Math.random() * users.length)]; const headers = { Authorization: `Bearer ${user.token}` }; const listRes = http.get(`${API_ORIGIN}/api/v1/playlists`, { headers }); const listOk = check(listRes, { 'playlists list returns 200': (r) => r.status === 200, }); errorRate.add(!listOk); sleep(0.5); if (listRes.status === 200) { try { const body = JSON.parse(listRes.body); const playlists = body.data?.playlists || body.data || body.playlists || []; if (Array.isArray(playlists) && playlists.length > 0) { const pl = playlists[0]; const plId = pl.id || pl.ID; if (plId) { const detailRes = http.get(`${API_ORIGIN}/api/v1/playlists/${plId}`, { headers }); check(detailRes, { 'playlist detail returns 200': (r) => r.status === 200 }); } } } catch (e) {} } if (Math.random() < 0.3) { const createRes = http.post( `${API_ORIGIN}/api/v1/playlists`, JSON.stringify({ name: `LoadTest ${Date.now()}`, description: 'k6 load test' }), { headers: { ...headers, 'Content-Type': 'application/json' } } ); check(createRes, { 'create playlist returns 201 or 200': (r) => r.status === 201 || r.status === 200 }); } const searchRes = http.get(`${API_ORIGIN}/api/v1/playlists/search?q=test`, { headers }); check(searchRes, { 'playlists search returns 200': (r) => r.status === 200 }); sleep(1); }