149 lines
5.1 KiB
JavaScript

/**
* SCENARIO: sos-flow.js
* SOS trigger → Guardian acknowledge → Status resolution
*
* SOS adalah fitur PALING KRITIS di WalkGuide — life-safety feature.
* Harus memiliki latency terendah di seluruh sistem.
*
* Target: p95 < 200ms, error rate = 0% (zero tolerance)
*/
import { sleep, check } from "k6";
import {
DEFAULT_THRESHOLDS,
handleSummary,
} from "../modules/metrics-helper.js";
import * as api from "../modules/api-client.js";
import { testData } from "../modules/test-data.js";
export const options = {
scenarios: {
sos_critical: {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "15s", target: 5 },
{ duration: "60s", target: 20 },
{ duration: "60s", target: 20 },
{ duration: "15s", target: 0 },
],
gracefulRampDown: "10s",
},
},
thresholds: {
...DEFAULT_THRESHOLDS,
// SOS: zero error tolerance, ultra-low latency
walkguide_sos_latency_ms: ["p(95)<200", "p(99)<400", "max<800"],
walkguide_error_rate: ["rate<0.005"], // < 0.5% error untuk SOS
http_req_failed: ["rate<0.005"],
},
};
// ── Per-VU state ──────────────────────────────────────────────────────────────
let userToken = null;
let guardianToken = null;
export default function sosFlow() {
// ── Init: setup user + guardian pair ──────────────────────────────────────
if (!userToken || !guardianToken) {
const suffix = `sos_${__VU}_${Date.now()}`;
// Register user
const uRes = api.register(testData.userRegisterPayload(suffix));
if (uRes.status === 200) {
const b = api.parseBody(uRes);
if (b && b.data) userToken = b.data.accessToken;
}
// Register guardian
const gRes = api.register(testData.guardianRegisterPayload(suffix));
if (gRes.status === 200) {
const b = api.parseBody(gRes);
if (b && b.data) guardianToken = b.data.accessToken;
}
if (!userToken || !guardianToken) {
console.error(`[sos-flow] VU ${__VU} failed to get tokens`);
sleep(2);
return;
}
sleep(0.5);
}
// ── Step 1: User trigger SOS ───────────────────────────────────────────────
const sosTriggerTypes = ["VOICE_COMMAND", "BUTTON", "MANUAL"];
const triggerType = sosTriggerTypes[__ITER % 3];
const coord = testData.randomSurabayaCoord(0.03);
const startTs = Date.now();
const sosRes = api.triggerSos(userToken, triggerType, coord.lat, coord.lng);
const triggerMs = Date.now() - startTs;
const sosOk = check(sosRes, {
"sos-trigger: status 2xx": (r) => r.status >= 200 && r.status < 300,
"sos-trigger: latency < 200ms": (r) => r.timings.duration < 200,
"sos-trigger: has sosId": (r) => {
const b = api.parseBody(r);
return b && b.data && b.data.id !== undefined;
},
"sos-trigger: status TRIGGERED": (r) => {
const b = api.parseBody(r);
return b && b.data && b.data.status === "TRIGGERED";
},
});
let sosId = null;
if (sosRes.status >= 200 && sosRes.status < 300) {
const body = api.parseBody(sosRes);
sosId = body && body.data ? body.data.id : null;
}
if (!sosOk) {
console.warn(
`[sos-flow] SOS trigger not OK for VU ${__VU}, trigger latency: ${triggerMs}ms`,
);
}
sleep(0.2);
// ── Step 2: Guardian poll SOS events ──────────────────────────────────────
const sosListRes = api.getSosEvents(guardianToken);
check(sosListRes, {
"guardian-sos-list: 2xx": (r) => r.status >= 200 && r.status < 300,
});
sleep(0.3);
// ── Step 3: Guardian acknowledge SOS ──────────────────────────────────────
if (sosId) {
const ackRes = api.acknowledgeSos(guardianToken, sosId);
check(ackRes, {
"sos-acknowledge: 2xx": (r) => r.status >= 200 && r.status < 300,
"sos-acknowledge: status ACKNOWLEDGED": (r) => {
const b = api.parseBody(r);
return b && b.data && b.data.status === "ACKNOWLEDGED";
},
});
}
sleep(0.5);
// ── Step 4: User check activity logs (SOS harus ter-log) ──────────────────
const logsRes = api.getActivityLogs(userToken, 0, 5);
check(logsRes, {
"activity-logs: 2xx": (r) => r.status >= 200 && r.status < 300,
});
// ── Think time antar SOS (SOS tidak terjadi setiap detik di production) ────
sleep(testData.randomFloat(1.0, 3.0, 1));
}
export function setup() {
const res = api.ping();
if (res.status !== 200)
throw new Error(`Backend not reachable: ${res.status}`);
console.log("🚨 SOS flow test starting — Zero error tolerance mode.");
}
export { handleSummary };