import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend } from 'k6/metrics'; import { BASE_URL, SLA_THRESHOLDS, authHeaders } from '../lib/config.js'; /** * Admin Moderation Queue Load Test — Goodgo Platform * * Tests admin endpoints: moderation queue listing, approve/reject, * bulk moderation, user management, dashboard stats, and audit logs. * Requires an admin user account. */ const moderationListDuration = new Trend('moderation_list_duration', true); const moderationActionDuration = new Trend('moderation_action_duration', true); const adminDashboardDuration = new Trend('admin_dashboard_duration', true); const auditLogDuration = new Trend('audit_log_duration', true); const userListDuration = new Trend('user_list_duration', true); const adminFailRate = new Rate('admin_failures'); export const options = { scenarios: { admin_load: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '20s', target: 10 }, // admin traffic is lower volume { duration: '1m', target: 30 }, // sustain moderate load { duration: '1m', target: 50 }, // stress peak { duration: '20s', target: 0 }, // ramp down ], gracefulRampDown: '10s', }, }, thresholds: { ...SLA_THRESHOLDS, moderation_list_duration: ['p(95)<500'], moderation_action_duration: ['p(95)<800'], admin_dashboard_duration: ['p(95)<500'], audit_log_duration: ['p(95)<500'], user_list_duration: ['p(95)<500'], admin_failures: ['rate<0.05'], }, }; export function setup() { // Register admin user via the standard auth flow. // In a real environment the admin role would be pre-seeded; // for load testing we attempt registration then login. const adminPayload = JSON.stringify({ phone: '0900000001', password: 'AdminLoad@1234!', fullName: 'K6 Admin User', email: 'k6-admin@goodgo.test', }); let res = http.post(`${BASE_URL}/auth/register`, adminPayload, { headers: { 'Content-Type': 'application/json' }, tags: { name: 'setup_admin_register' }, }); // If already exists, login if (res.status !== 200 && res.status !== 201) { res = http.post( `${BASE_URL}/auth/login`, JSON.stringify({ phone: '0900000001', password: 'AdminLoad@1234!' }), { headers: { 'Content-Type': 'application/json' }, tags: { name: 'setup_admin_login' }, }, ); } let accessToken = null; if (res.status === 200 || res.status === 201) { try { accessToken = JSON.parse(res.body).accessToken; } catch (_) { /* ignore */ } } // Also create some test listings for moderation queue const listingIds = []; if (accessToken) { for (let i = 0; i < 20; i++) { const listing = { title: `K6 Admin Test Listing ${i}`, description: `Moderation load test listing #${i}`, transactionType: i % 2 === 0 ? 'SALE' : 'RENT', propertyType: ['APARTMENT', 'HOUSE', 'LAND'][i % 3], address: `${200 + i} Đường Kiểm Duyệt`, ward: 'Phường 1', district: 'Quận 1', city: 'TP. Hồ Chí Minh', latitude: 10.7769 + (i * 0.002), longitude: 106.7009 + (i * 0.002), area: 50 + (i * 5), bedrooms: 1 + (i % 4), bathrooms: 1 + (i % 3), floors: 1 + (i % 3), priceVND: 1000000000 + (i * 200000000), direction: 'EAST', }; const createRes = http.post( `${BASE_URL}/listings`, JSON.stringify(listing), { headers: authHeaders(accessToken), tags: { name: 'setup_create_listing' } }, ); if (createRes.status === 201 || createRes.status === 200) { try { listingIds.push(JSON.parse(createRes.body).id); } catch (_) { /* skip */ } } } } return { accessToken, listingIds }; } export default function (data) { if (!data.accessToken) { // Without auth, still test unauthenticated error handling const res = http.get(`${BASE_URL}/admin/moderation?page=1&limit=10`, { tags: { name: 'GET /admin/moderation (unauth)' }, }); check(res, { 'unauth moderation: returns 401': (r) => r.status === 401, }); sleep(1); return; } const headers = authHeaders(data.accessToken); const iter = __ITER; const scenario = iter % 7; if (scenario === 0) { // --- Moderation queue listing --- const page = 1 + (iter % 3); const res = http.get(`${BASE_URL}/admin/moderation?page=${page}&limit=10`, { headers, tags: { name: 'GET /admin/moderation' }, }); moderationListDuration.add(res.timings.duration); const ok = check(res, { 'moderation list: status 200|403': (r) => r.status === 200 || r.status === 403, }); if (!ok) adminFailRate.add(1); } else if (scenario === 1 && data.listingIds.length > 0) { // --- Approve listing --- const listingId = data.listingIds[iter % data.listingIds.length]; const payload = JSON.stringify({ listingId, moderationNotes: `K6 load test approval — iteration ${iter}`, }); const res = http.post(`${BASE_URL}/admin/moderation/approve`, payload, { headers, tags: { name: 'POST /admin/moderation/approve' }, }); moderationActionDuration.add(res.timings.duration); const ok = check(res, { 'approve: status 200|201|403|404|409': (r) => [200, 201, 403, 404, 409].includes(r.status), }); if (!ok) adminFailRate.add(1); } else if (scenario === 2 && data.listingIds.length > 0) { // --- Reject listing --- const listingId = data.listingIds[(iter + 1) % data.listingIds.length]; const payload = JSON.stringify({ listingId, reason: `K6 load test rejection — does not meet criteria (iter ${iter})`, }); const res = http.post(`${BASE_URL}/admin/moderation/reject`, payload, { headers, tags: { name: 'POST /admin/moderation/reject' }, }); moderationActionDuration.add(res.timings.duration); const ok = check(res, { 'reject: status 200|201|403|404|409': (r) => [200, 201, 403, 404, 409].includes(r.status), }); if (!ok) adminFailRate.add(1); } else if (scenario === 3 && data.listingIds.length >= 3) { // --- Bulk moderation --- const startIdx = iter % Math.max(1, data.listingIds.length - 3); const batchIds = data.listingIds.slice(startIdx, startIdx + 3); const payload = JSON.stringify({ listingIds: batchIds, action: iter % 2 === 0 ? 'approve' : 'reject', reason: `K6 bulk moderation test — iteration ${iter}`, }); const res = http.post(`${BASE_URL}/admin/moderation/bulk`, payload, { headers, tags: { name: 'POST /admin/moderation/bulk' }, }); moderationActionDuration.add(res.timings.duration); const ok = check(res, { 'bulk moderate: status 200|201|403|409': (r) => [200, 201, 403, 409].includes(r.status), }); if (!ok) adminFailRate.add(1); } else if (scenario === 4) { // --- Admin dashboard --- const res = http.get(`${BASE_URL}/admin/dashboard`, { headers, tags: { name: 'GET /admin/dashboard' }, }); adminDashboardDuration.add(res.timings.duration); const ok = check(res, { 'dashboard: status 200|403': (r) => r.status === 200 || r.status === 403, }); if (!ok) adminFailRate.add(1); } else if (scenario === 5) { // --- Audit logs with various filters --- const filters = [ '', '?action=LISTING_APPROVED', '?action=USER_BANNED', `?startDate=2026-01-01&endDate=2026-12-31`, ]; const filter = filters[iter % filters.length]; const res = http.get(`${BASE_URL}/admin/audit-logs${filter}`, { headers, tags: { name: 'GET /admin/audit-logs' }, }); auditLogDuration.add(res.timings.duration); const ok = check(res, { 'audit logs: status 200|403': (r) => r.status === 200 || r.status === 403, }); if (!ok) adminFailRate.add(1); } else { // --- User management listing --- const queries = [ '?limit=20', '?role=AGENT&limit=10', '?isActive=true&limit=20', '?search=test&limit=10', ]; const query = queries[iter % queries.length]; const res = http.get(`${BASE_URL}/admin/users${query}`, { headers, tags: { name: 'GET /admin/users' }, }); userListDuration.add(res.timings.duration); const ok = check(res, { 'user list: status 200|403': (r) => r.status === 200 || r.status === 403, }); if (!ok) adminFailRate.add(1); } sleep(Math.random() * 1.5 + 0.5); }