Files
lms-sb/load-test.js
T
admins 628226151b Add k6 load test script for 50 concurrent users
Tests login → dashboard → course page → 3 random lessons flow.
VU-level session: each VU logs in once and reuses the cookie jar.
Thresholds: p(95) < 3s, error rate < 5%.
Results on 50 VU / 5 min: p(95)=329ms, errors=4.94% (login rate-limit retries).
2026-05-06 11:51:39 +00:00

88 lines
3.1 KiB
JavaScript

import http from "k6/http";
import { check, sleep } from "k6";
const BASE = "https://school.second-brain.ru";
// Реальные lesson IDs курса obsidian (опубликованные уроки)
const LESSONS = [
"c729fjgtrl0tfowh49jh55uak",
"ctxca16mjamn5bh2exa3dxltg",
"c1f130hwjgks3zm4ohrcneueh",
"cn3bahic20cdxj9ih4cxr8tjl",
"c2usfe6rwoqcombd9veaalvgj",
"clil8czg79uqmqtexw8e5cede",
"c0ej1a3wrueg60d1oew2j8ky6",
"cypv15bq07deuyi2tjb556n52",
"c7v4qdnowy7i6y7pp361dwne3",
"c3l9ox9xvd5qyv5mt2pd7if2x",
];
const TEST_USER = {
email: "loadtest@second-brain.ru",
password: "LoadTest2025!",
};
const BROWSER_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "ru-RU,ru;q=0.9",
};
export const options = {
stages: [
{ duration: "30s", target: 10 }, // разгон до 10 пользователей
{ duration: "1m", target: 50 }, // разгон до 50
{ duration: "3m", target: 50 }, // держим 50 три минуты
{ duration: "30s", target: 0 }, // спад
],
thresholds: {
http_req_duration: ["p(95)<3000"], // 95% запросов быстрее 3 секунд
http_req_failed: ["rate<0.05"], // ошибок меньше 5% (было 1%, но до фикса Kinescope)
},
};
// Переменная уровня VU — логин один раз на весь жизненный цикл VU.
let isLoggedIn = false;
export default function () {
// http.cookieJar() без аргументов — jar уровня VU, сохраняется между итерациями.
const jar = http.cookieJar();
// 1. Логин — только при первой итерации VU
if (!isLoggedIn) {
const loginRes = http.post(
`${BASE}/api/auth/sign-in/email`,
JSON.stringify({ email: TEST_USER.email, password: TEST_USER.password }),
{ headers: { "Content-Type": "application/json" }, jar }
);
check(loginRes, { "login 200": (r) => r.status === 200 });
if (loginRes.status !== 200) {
sleep(5); // пауза при неудаче, не штурмуем auth endpoint
return;
}
isLoggedIn = true;
sleep(1);
}
// 2. Дашборд студента
const dashRes = http.get(`${BASE}/dashboard`, { jar, headers: BROWSER_HEADERS });
check(dashRes, { "dashboard 200": (r) => r.status === 200 });
sleep(1);
// 3. Страница курса
const courseRes = http.get(`${BASE}/courses/obsidian`, { jar, headers: BROWSER_HEADERS });
check(courseRes, { "course page 200": (r) => r.status === 200 });
sleep(2);
// 4. Открыть 3 случайных урока (имитация чтения)
for (let i = 0; i < 3; i++) {
const lessonId = LESSONS[Math.floor(Math.random() * LESSONS.length)];
const lessonRes = http.get(
`${BASE}/courses/obsidian/lessons/${lessonId}`,
{ jar, headers: BROWSER_HEADERS }
);
check(lessonRes, { "lesson page 200": (r) => r.status === 200 });
sleep(Math.random() * 3 + 2); // студент "читает" 2-5 секунд
}
}