// k6 load-test: authentication login flow. // // Profile: 50 VUs ramping over 30s, holding briefly to gather a stable // percentile sample. The login endpoint is the most security-critical write // path, so the failure-rate threshold is tighter than the read path and the // p(99) latency budget covers password hashing + session creation. // // Run: // K6_TARGET_URL=http://localhost:3000 k6 run tests/load/login.js // // Optional env: // LOAD_LOGIN_EMAIL test account email (default: load-test@example.com) // LOAD_LOGIN_PASSWORD test account password (default: load-test-password) import http from "k6/http"; import { check, group } from "k6"; export const options = { scenarios: { login: { executor: "ramping-vus", startVUs: 0, stages: [ { duration: "30s", target: 50 }, { duration: "30s", target: 50 }, { duration: "10s", target: 0 }, ], gracefulRampDown: "10s", tags: { suite: "login" }, }, }, thresholds: { http_req_failed: ["rate<0.05"], http_req_duration: ["p(99)<800"], "http_req_duration{name:login}": ["p(95)<400"], }, // Avoid surprising the local server with default 100s connection lifetimes // when this script is run as part of the composite suite. noConnectionReuse: false, discardResponseBodies: true, }; const TARGET_URL = __ENV.K6_TARGET_URL || "http://localhost:3000"; const EMAIL = __ENV.LOAD_LOGIN_EMAIL || "load-test@example.com"; const PASSWORD = __ENV.LOAD_LOGIN_PASSWORD || "load-test-password"; export default function loginScenario() { group("login", function () { const payload = JSON.stringify({ email: EMAIL, password: PASSWORD }); const params = { headers: { "content-type": "application/json" }, tags: { name: "login" }, }; const res = http.post(`${TARGET_URL}/v1/auth/login`, payload, params); check(res, { "status is 200 or 401": (r) => r.status === 200 || r.status === 401, "has body": (r) => r.body === null || r.body !== undefined, }); }); }