Files
goodgo-platform/load-tests/scripts/auth.js
Ho Ngoc Hai a8e1a438b9 feat(load-tests): add K6 load testing suite for critical API paths
K6 scripts for 4 critical paths:
- Auth (100 VU): login, register, profile
- Listings (500 VU): search with filters, detail view
- Search (200 VU): full-text + geo search
- Payments (50 VU): create payment, list transactions

SLA thresholds: p50<200ms, p95<500ms, p99<1s, error<1%.
CI: manual workflow_dispatch with suite selector.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-09 08:41:15 +07:00

122 lines
3.6 KiB
JavaScript

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