import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend } from 'k6/metrics'; import { BASE_URL, SLA_THRESHOLDS, uniquePhone } from '../lib/config.js'; const loginDuration = new Trend('login_duration', true); const registerDuration = new Trend('register_duration', true); const loginFailRate = new Rate('login_failures'); export const options = { scenarios: { auth_load: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '30s', target: 50 }, // ramp up { duration: '1m', target: 100 }, // sustain 100 concurrent { duration: '30s', target: 0 }, // ramp down ], gracefulRampDown: '10s', }, }, thresholds: { ...SLA_THRESHOLDS, login_duration: ['p(95)<500'], register_duration: ['p(95)<800'], login_failures: ['rate<0.05'], }, }; export function setup() { // Pre-register a pool of users for login tests const users = []; for (let i = 0; i < 50; i++) { const phone = `0910${String(i).padStart(6, '0')}`; const payload = JSON.stringify({ phone, password: 'LoadTest@1234!', fullName: `K6 Auth User ${i}`, email: `k6-auth-${i}@goodgo.test`, }); const res = http.post(`${BASE_URL}/auth/register`, payload, { headers: { 'Content-Type': 'application/json' }, tags: { name: 'setup_register' }, }); if (res.status === 200 || res.status === 201 || res.status === 409) { users.push({ phone, password: 'LoadTest@1234!' }); } } return { users }; } export default function (data) { const vu = __VU; const iter = __ITER; // Alternate between register and login if (iter % 3 === 0) { // Register new user const phone = uniquePhone(); const payload = JSON.stringify({ phone, password: 'LoadTest@1234!', fullName: `K6 New User ${phone}`, email: `k6-new-${phone}@goodgo.test`, }); const res = http.post(`${BASE_URL}/auth/register`, payload, { headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /auth/register' }, }); registerDuration.add(res.timings.duration); check(res, { 'register: status 200|201': (r) => r.status === 200 || r.status === 201, 'register: has accessToken': (r) => { try { return JSON.parse(r.body).accessToken !== undefined; } catch { return false; } }, }); } else { // Login existing user const user = data.users[vu % data.users.length]; if (!user) return; const res = http.post( `${BASE_URL}/auth/login`, JSON.stringify({ phone: user.phone, password: user.password }), { headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /auth/login' }, }, ); loginDuration.add(res.timings.duration); const ok = check(res, { 'login: status 200|201': (r) => r.status === 200 || r.status === 201, 'login: has accessToken': (r) => { try { return JSON.parse(r.body).accessToken !== undefined; } catch { return false; } }, }); loginFailRate.add(!ok); // Optionally hit profile with the token if (res.status === 200 || res.status === 201) { try { const tokens = JSON.parse(res.body); const profileRes = http.get(`${BASE_URL}/auth/profile`, { headers: { Authorization: `Bearer ${tokens.accessToken}` }, tags: { name: 'GET /auth/profile' }, }); check(profileRes, { 'profile: status 200': (r) => r.status === 200, }); } catch (_) { /* ignore parse errors */ } } } sleep(Math.random() * 2 + 0.5); }