Files
Tomáš DvořákandDevin 8853bf5f59
CI / Frontend (push) Canceled after 0s
CI / Go - apps/backend (push) Canceled after 0s
CI / Docker publish - backend (push) Canceled after 0s
test(load): k6 smoke for public booking surface (#22)
First load script: healthz + meta/config + tenant availability/catalog,
booking POST gated behind WRITE=1. Env-driven BASE_URL/VUS/DURATION so
the same file runs locally (smoke) and from the Proxmox runner against
a deployed target (load). 404 on unknown tenant slug counted as an
expected response.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-26 17:03:00 +02:00

74 lines
2.7 KiB
JavaScript

// k6 smoke/load test — Bookra public booking surface.
//
// Local smoke (script validation, not perf evidence — loopback latency is ~0ms):
// k6 run tests/load/bookings.js
// Load against a deployed target (run from the Proxmox host, not your box):
// BASE_URL=https://api.bookra.eu VUS=50 DURATION=3m k6 run tests/load/bookings.js
// Include a booking-creation pass (writes a real row — point at a test tenant):
// WRITE=1 TENANT_SLUG=test-tenant BASE_URL=... k6 run tests/load/bookings.js
//
// Endpoints exercised: /healthz, /v1/meta/config, public availability + catalog,
// and (WRITE=1) POST /v1/public/bookings — the money path, rate-limited.
import http from "k6/http";
import { check, sleep, group } from "k6";
const BASE_URL = (__ENV.BASE_URL || "http://localhost:8080").replace(/\/$/, "");
const TENANT = __ENV.TENANT_SLUG || "demo";
const WRITE = __ENV.WRITE === "1";
export const options = {
scenarios: {
booking_surface: {
executor: "constant-vus",
vus: Number(__ENV.VUS || 2),
duration: __ENV.DURATION || "30s",
},
},
thresholds: {
// 800ms covers a cold local dev server; tighten to 400 once aimed at prod.
http_req_duration: ["p(95)<800"],
http_req_failed: ["rate<0.01"],
"checks{check:public reads ok}": ["rate>0.99"],
},
};
export default function () {
group("public reads", () => {
// 404 on an unknown tenant slug is a correct answer, not a failure —
// mark it expected so http_req_failed stays meaningful.
const tenantOk = { responseCallback: http.expectedStatuses(200, 404) };
const hz = http.get(`${BASE_URL}/healthz`);
const cfg = http.get(`${BASE_URL}/v1/meta/config`);
const cat = http.get(`${BASE_URL}/v1/public/tenants/${TENANT}/catalog`, tenantOk);
const av = http.get(`${BASE_URL}/v1/public/tenants/${TENANT}/availability`, tenantOk);
check(null, {
"public reads ok":
hz.status === 200 &&
cfg.status === 200 &&
[200, 404].includes(cat.status) &&
[200, 404].includes(av.status),
});
});
if (WRITE) {
group("booking create", () => {
const res = http.post(
`${BASE_URL}/v1/public/bookings`,
JSON.stringify({
tenantSlug: TENANT,
serviceId: __ENV.SERVICE_ID || "svc_loadtest",
startsAt: new Date(Date.now() + 864e5).toISOString(),
customer: { name: "k6 loadtest", email: "[email protected]" },
}),
{ headers: { "Content-Type": "application/json" } },
);
// 201/200 created, 400/404 validation — all prove the handler runs;
// 5xx and 429-at-low-VUS are the failures that matter.
check(res, { "booking handler healthy": (r) => r.status < 500 });
});
}
sleep(0.5);
}