import http from 'k6/http'; import { check, sleep } from 'k6'; const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080'; const ADMIN_EMAIL = __ENV.ADMIN_EMAIL || 'admin@azaion.com'; const ADMIN_PASSWORD = __ENV.ADMIN_PASSWORD || 'Admin1234'; export const options = { scenarios: { nft_perf_01_login: { executor: 'constant-vus', vus: 10, duration: '30s', exec: 'login', tags: { scenario: 'nft_perf_01_login' }, }, nft_perf_04_user_list: { executor: 'constant-vus', vus: 10, duration: '30s', exec: 'userList', tags: { scenario: 'nft_perf_04_user_list' }, startTime: '35s', }, }, thresholds: { 'http_req_duration{scenario:nft_perf_01_login}': ['p(95)<500'], 'http_req_duration{scenario:nft_perf_04_user_list}': ['p(95)<1000'], 'http_req_failed{scenario:nft_perf_01_login}': ['rate<0.01'], 'http_req_failed{scenario:nft_perf_04_user_list}': ['rate<0.01'], }, }; // setup() runs once before all VUs. Pre-fetch a JWT so the user-list scenario // measures only the listing path, not login latency. export function setup() { const res = http.post( `${BASE_URL}/login`, JSON.stringify({ Email: ADMIN_EMAIL, Password: ADMIN_PASSWORD }), { headers: { 'Content-Type': 'application/json' } } ); if (res.status !== 200) { throw new Error(`setup: login failed (status ${res.status}): ${res.body}`); } const body = JSON.parse(res.body); const token = body.token || body.Token; if (!token) { throw new Error(`setup: login response missing Token: ${res.body}`); } return { token }; } export function login() { const res = http.post( `${BASE_URL}/login`, JSON.stringify({ Email: ADMIN_EMAIL, Password: ADMIN_PASSWORD }), { headers: { 'Content-Type': 'application/json' }, tags: { scenario: 'nft_perf_01_login' }, } ); check(res, { 'login status 200': (r) => r.status === 200, 'login returned token': (r) => { try { const body = JSON.parse(r.body); return !!(body.token || body.Token); } catch { return false; } }, }); sleep(0.1); } export function userList(data) { const res = http.get(`${BASE_URL}/users`, { headers: { Authorization: `Bearer ${data.token}` }, tags: { scenario: 'nft_perf_04_user_list' }, }); check(res, { 'user list status 200': (r) => r.status === 200, 'user list returned >= 500 users': (r) => { try { const arr = JSON.parse(r.body); return Array.isArray(arr) && arr.length >= 500; } catch { return false; } }, }); sleep(0.1); } export function handleSummary(data) { return { 'e2e/test-results/perf-summary.json': JSON.stringify(data, null, 2), stdout: textSummary(data), }; } function textSummary(data) { const lines = []; lines.push(''); lines.push('=== PERF SUMMARY ==='); for (const [name, metric] of Object.entries(data.metrics)) { if (!name.startsWith('http_req_duration') && !name.startsWith('http_req_failed')) continue; const vals = metric.values || {}; const parts = []; if (vals['p(50)'] !== undefined) parts.push(`p50=${vals['p(50)'].toFixed(1)}ms`); if (vals['p(95)'] !== undefined) parts.push(`p95=${vals['p(95)'].toFixed(1)}ms`); if (vals['p(99)'] !== undefined) parts.push(`p99=${vals['p(99)'].toFixed(1)}ms`); if (vals.rate !== undefined) parts.push(`rate=${(vals.rate * 100).toFixed(2)}%`); if (vals.count !== undefined) parts.push(`count=${vals.count}`); lines.push(` ${name}: ${parts.join(' ยท ')}`); } if (data.root_group && data.root_group.checks) { const checks = data.root_group.checks; if (checks.length > 0) { lines.push('--- checks ---'); for (const c of checks) { lines.push(` ${c.name}: ${c.passes} pass / ${c.fails} fail`); } } } lines.push('===================='); return lines.join('\n') + '\n'; }