feat: Add user role retrieval, enhance recommended course filtering and detail, and introduce new k6 load tests.
This commit is contained in:
parent
c118e5c3dc
commit
ef70d2db3f
11 changed files with 515 additions and 139 deletions
160
Backend/tests/k6/enroll-load-test.js
Normal file
160
Backend/tests/k6/enroll-load-test.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Backend/tests/k6/enroll-load-test.js
|
||||
//
|
||||
// จำลองนักเรียนหลายคน login แล้ว enroll คอร์สพร้อมกัน
|
||||
//
|
||||
// Flow:
|
||||
// 1. Login
|
||||
// 2. Enroll คอร์ส
|
||||
// 3. ตรวจสอบ enrolled courses
|
||||
//
|
||||
// Usage:
|
||||
// k6 run -e APP_URL=http://192.168.1.137:4000 -e COURSE_ID=1 tests/k6/enroll-load-test.js
|
||||
|
||||
import http from 'k6/http';
|
||||
import { check, sleep, group } from 'k6';
|
||||
import { Rate, Trend, Counter } from 'k6/metrics';
|
||||
import { SharedArray } from 'k6/data';
|
||||
|
||||
// ─── Custom Metrics ───────────────────────────────────────────────────────────
|
||||
const errorRate = new Rate('errors');
|
||||
const loginTime = new Trend('login_duration', true);
|
||||
const enrollTime = new Trend('enroll_duration', true);
|
||||
const enrolledCount = new Counter('successful_enrollments');
|
||||
|
||||
// ─── Load student credentials ─────────────────────────────────────────────────
|
||||
const students = new SharedArray('students', function () {
|
||||
return JSON.parse(open('./test-credentials.json')).students;
|
||||
});
|
||||
|
||||
// ─── Config ───────────────────────────────────────────────────────────────────
|
||||
const BASE_URL = __ENV.APP_URL || 'http://192.168.1.137:4000';
|
||||
const COURSE_ID = __ENV.COURSE_ID || '1';
|
||||
|
||||
// ─── Test Options ─────────────────────────────────────────────────────────────
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '20s', target: 10 }, // Ramp up
|
||||
{ duration: '1m', target: 30 }, // Increase
|
||||
{ duration: '30s', target: 50 }, // Peak: 50 คน enroll พร้อมกัน
|
||||
{ duration: '30s', target: 0 }, // Ramp down
|
||||
],
|
||||
thresholds: {
|
||||
'login_duration': ['p(95)<2000'], // Login < 2s
|
||||
'enroll_duration': ['p(95)<1000'], // Enroll < 1s
|
||||
'errors': ['rate<0.05'],
|
||||
'http_req_failed': ['rate<0.05'],
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Helper ───────────────────────────────────────────────────────────────────
|
||||
function jsonHeaders(token) {
|
||||
const h = { 'Content-Type': 'application/json' };
|
||||
if (token) h['Authorization'] = `Bearer ${token}`;
|
||||
return h;
|
||||
}
|
||||
|
||||
// ─── Main ─────────────────────────────────────────────────────────────────────
|
||||
export default function () {
|
||||
const student = students[__VU % students.length];
|
||||
let token = null;
|
||||
|
||||
// ── Step 1: Login ──────────────────────────────────────────────────────────
|
||||
group('1. Login', () => {
|
||||
const res = http.post(
|
||||
`${BASE_URL}/api/auth/login`,
|
||||
JSON.stringify({ email: student.email, password: student.password }),
|
||||
{ headers: jsonHeaders(null) }
|
||||
);
|
||||
|
||||
loginTime.add(res.timings.duration);
|
||||
errorRate.add(res.status !== 200);
|
||||
|
||||
check(res, {
|
||||
'login: status 200': (r) => r.status === 200,
|
||||
'login: has token': (r) => { try { return !!r.json('data.token'); } catch { return false; } },
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
try { token = res.json('data.token'); } catch {}
|
||||
}
|
||||
});
|
||||
|
||||
if (!token) {
|
||||
console.warn(`[VU ${__VU}] Login failed for ${student.email} — skipping`);
|
||||
sleep(1);
|
||||
return;
|
||||
}
|
||||
|
||||
sleep(0.5);
|
||||
|
||||
// ── Step 2: Enroll ─────────────────────────────────────────────────────────
|
||||
group('2. Enroll Course', () => {
|
||||
const res = http.post(
|
||||
`${BASE_URL}/api/students/courses/${COURSE_ID}/enroll`,
|
||||
null,
|
||||
{ headers: jsonHeaders(token) }
|
||||
);
|
||||
|
||||
enrollTime.add(res.timings.duration);
|
||||
|
||||
// 200 = enrolled, 409 = already enrolled (ถือว่าโอเค)
|
||||
const ok = res.status === 200 || res.status === 409;
|
||||
errorRate.add(!ok);
|
||||
|
||||
if (res.status === 200) enrolledCount.add(1);
|
||||
|
||||
check(res, {
|
||||
'enroll: 200 or 409': (r) => r.status === 200 || r.status === 409,
|
||||
'enroll: fast response': (r) => r.timings.duration < 1000,
|
||||
});
|
||||
});
|
||||
|
||||
sleep(0.5);
|
||||
|
||||
// ── Step 3: Verify — ดึงรายการคอร์สที่ลงทะเบียน ─────────────────────────
|
||||
group('3. Get Enrolled Courses', () => {
|
||||
const res = http.get(
|
||||
`${BASE_URL}/api/students/courses`,
|
||||
{ headers: jsonHeaders(token) }
|
||||
);
|
||||
|
||||
errorRate.add(res.status !== 200);
|
||||
|
||||
check(res, {
|
||||
'enrolled courses: status 200': (r) => r.status === 200,
|
||||
});
|
||||
});
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
// ─── Summary ──────────────────────────────────────────────────────────────────
|
||||
export function handleSummary(data) {
|
||||
const m = data.metrics;
|
||||
const avg = (k) => m[k]?.values?.avg?.toFixed(0) ?? 'N/A';
|
||||
const p95 = (k) => m[k]?.values?.['p(95)']?.toFixed(0) ?? 'N/A';
|
||||
const rate = (k) => ((m[k]?.values?.rate ?? 0) * 100).toFixed(2);
|
||||
const cnt = (k) => m[k]?.values?.count ?? 0;
|
||||
|
||||
return {
|
||||
stdout: `
|
||||
╔══════════════════════════════════════════════════════════╗
|
||||
║ Course Enroll — Load Test ║
|
||||
╠══════════════════════════════════════════════════════════╣
|
||||
║ Course ID : ${String(COURSE_ID).padEnd(43)}║
|
||||
╠══════════════════════════════════════════════════════════╣
|
||||
║ RESPONSE TIMES (avg / p95) ║
|
||||
║ Login : ${avg('login_duration')}ms / ${p95('login_duration')}ms
|
||||
║ Enroll : ${avg('enroll_duration')}ms / ${p95('enroll_duration')}ms
|
||||
╠══════════════════════════════════════════════════════════╣
|
||||
║ COUNTS ║
|
||||
║ Total Requests : ${String(cnt('http_reqs')).padEnd(33)}║
|
||||
║ New Enrollments : ${String(cnt('successful_enrollments')).padEnd(33)}║
|
||||
╠══════════════════════════════════════════════════════════╣
|
||||
║ ERROR RATES ║
|
||||
║ HTTP Failed : ${(rate('http_req_failed') + '%').padEnd(39)}║
|
||||
║ Custom Errors : ${(rate('errors') + '%').padEnd(39)}║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
`,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue