249 lines
6.9 KiB
JavaScript
249 lines
6.9 KiB
JavaScript
import http from "k6/http";
|
|
import { check, sleep, group } from "k6";
|
|
import { Counter, Trend } from "k6/metrics";
|
|
|
|
// ---------------- Configuration ----------------
|
|
export const options = {
|
|
// Adjust vus/duration for your environment
|
|
scenarios: {
|
|
steady_mutations: {
|
|
executor: "constant-vus",
|
|
vus: __ENV.VUS ? parseInt(__ENV.VUS, 10) : 20,
|
|
duration: __ENV.DURATION || "5m",
|
|
gracefulStop: "30s",
|
|
},
|
|
ramp_up: {
|
|
executor: "ramping-vus",
|
|
startVUs: 0,
|
|
stages: [
|
|
{
|
|
duration: "1m",
|
|
target: __ENV.RAMP_TARGET
|
|
? parseInt(__ENV.RAMP_TARGET, 10)
|
|
: 50,
|
|
},
|
|
{
|
|
duration: "1m",
|
|
target: __ENV.RAMP_TARGET
|
|
? parseInt(__ENV.RAMP_TARGET, 10)
|
|
: 50,
|
|
},
|
|
{ duration: "1m", target: 0 },
|
|
],
|
|
gracefulStop: "30s",
|
|
startTime: "30s",
|
|
},
|
|
},
|
|
thresholds: {
|
|
http_req_failed: ["rate<0.02"], // < 2% failures
|
|
http_req_duration: ["p(90)<800", "p(99)<1500"], // latency SLO
|
|
"cart_add_item_duration{op:add}": ["p(90)<800"],
|
|
"cart_fetch_duration{op:get}": ["p(90)<600"],
|
|
},
|
|
summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"],
|
|
};
|
|
|
|
// ---------------- Metrics ----------------
|
|
const addItemTrend = new Trend("cart_add_item_duration", true);
|
|
const fetchTrend = new Trend("cart_fetch_duration", true);
|
|
const checkoutTrend = new Trend("cart_checkout_duration", true);
|
|
const addedItemsCounter = new Counter("cart_items_added");
|
|
const checkoutCounter = new Counter("cart_checkout_calls");
|
|
|
|
// ---------------- SKUs ----------------
|
|
const SKUS = [
|
|
"778290",
|
|
"778345",
|
|
"778317",
|
|
"778277",
|
|
"778267",
|
|
"778376",
|
|
"778244",
|
|
"778384",
|
|
"778365",
|
|
"778377",
|
|
"778255",
|
|
"778286",
|
|
"778246",
|
|
"778270",
|
|
"778266",
|
|
"778285",
|
|
"778329",
|
|
"778425",
|
|
"778407",
|
|
"778418",
|
|
"778430",
|
|
"778469",
|
|
"778358",
|
|
"778351",
|
|
"778319",
|
|
"778307",
|
|
"778278",
|
|
"778251",
|
|
"778253",
|
|
"778261",
|
|
"778263",
|
|
"778273",
|
|
"778281",
|
|
"778294",
|
|
"778297",
|
|
"778302",
|
|
];
|
|
|
|
// ---------------- Helpers ----------------
|
|
function randomSku() {
|
|
return SKUS[Math.floor(Math.random() * SKUS.length)];
|
|
}
|
|
function randomQty() {
|
|
return 1 + Math.floor(Math.random() * 3); // 1..3
|
|
}
|
|
function baseUrl() {
|
|
const u = __ENV.BASE_URL || "http://localhost:8080/cart";
|
|
// Allow user to pass either root host or full /cart path
|
|
return u.endsWith("/cart") ? u : u.replace(/\/+$/, "") + "/cart";
|
|
}
|
|
function extractCookie(res, name) {
|
|
const cookies = res.cookies[name];
|
|
if (!cookies || cookies.length === 0) return null;
|
|
return cookies[0].value;
|
|
}
|
|
function withCookies(headers, cookieJar) {
|
|
if (!cookieJar || Object.keys(cookieJar).length === 0) return headers;
|
|
const cookieStr = Object.entries(cookieJar)
|
|
.map(([k, v]) => `${k}=${v}`)
|
|
.join("; ");
|
|
return { ...headers, Cookie: cookieStr };
|
|
}
|
|
|
|
// Maintain cart + owner cookies per VU
|
|
let cartState = {
|
|
cartid: null,
|
|
cartowner: null,
|
|
};
|
|
|
|
// Refresh cookies from response
|
|
function updateCookies(res) {
|
|
const cid = extractCookie(res, "cartid");
|
|
if (cid) cartState.cartid = cid;
|
|
const owner = extractCookie(res, "cartowner");
|
|
if (owner) cartState.cartowner = owner;
|
|
}
|
|
|
|
// Build headers
|
|
function headers() {
|
|
const h = { "Content-Type": "application/json" };
|
|
const jar = {};
|
|
if (cartState.cartid) jar["cartid"] = cartState.cartid;
|
|
if (cartState.cartowner) jar["cartowner"] = cartState.cartowner;
|
|
return withCookies(h, jar);
|
|
}
|
|
|
|
// Ensure cart exists (GET /)
|
|
function ensureCart() {
|
|
if (cartState.cartid) return;
|
|
const res = http.get(baseUrl() + "/", { headers: headers() });
|
|
updateCookies(res);
|
|
check(res, {
|
|
"ensure cart status 200": (r) => r.status === 200,
|
|
"ensure cart has id": () => !!cartState.cartid,
|
|
});
|
|
}
|
|
|
|
// Add random item
|
|
function addRandomItem() {
|
|
const payload = JSON.stringify({
|
|
sku: randomSku(),
|
|
quantity: randomQty(),
|
|
country: "no",
|
|
});
|
|
const start = Date.now();
|
|
const res = http.post(baseUrl(), payload, { headers: headers() });
|
|
const dur = Date.now() - start;
|
|
addItemTrend.add(dur, { op: "add" });
|
|
if (res.status === 200) {
|
|
addedItemsCounter.add(1);
|
|
}
|
|
updateCookies(res);
|
|
check(res, {
|
|
"add item status ok": (r) => r.status === 200,
|
|
});
|
|
}
|
|
|
|
// Fetch cart state
|
|
function fetchCart() {
|
|
const start = Date.now();
|
|
const res = http.get(baseUrl() + "/", { headers: headers() });
|
|
const dur = Date.now() - start;
|
|
fetchTrend.add(dur, { op: "get" });
|
|
updateCookies(res);
|
|
check(res, { "fetch status ok": (r) => r.status === 200 });
|
|
}
|
|
|
|
// Occasional checkout trigger
|
|
function maybeCheckout() {
|
|
if (!cartState.cartid) return;
|
|
// // Small probability
|
|
// if (Math.random() < 0.02) {
|
|
// const start = Date.now();
|
|
// const res = http.get(baseUrl() + "/checkout", { headers: headers() });
|
|
// const dur = Date.now() - start;
|
|
// checkoutTrend.add(dur, { op: "checkout" });
|
|
// updateCookies(res);
|
|
// if (res.status === 200) checkoutCounter.add(1);
|
|
// check(res, { "checkout status ok": (r) => r.status === 200 });
|
|
// }
|
|
}
|
|
|
|
// ---------------- k6 lifecycle ----------------
|
|
export function setup() {
|
|
// Provide SKU list length for summary
|
|
return { skuCount: SKUS.length };
|
|
}
|
|
|
|
export default function (data) {
|
|
group("cart flow", () => {
|
|
// Create or reuse cart
|
|
ensureCart();
|
|
|
|
// Random number of item mutations per iteration (1..5)
|
|
const ops = 1 + Math.floor(Math.random() * 5);
|
|
for (let i = 0; i < ops; i++) {
|
|
addRandomItem();
|
|
}
|
|
|
|
// Fetch state
|
|
fetchCart();
|
|
|
|
// Optional checkout attempt
|
|
maybeCheckout();
|
|
});
|
|
|
|
// Small think time
|
|
sleep(Math.random() * 0.5);
|
|
}
|
|
|
|
export function teardown(data) {
|
|
// Optionally we could GET confirmation or clear cart cookie
|
|
// Not implemented for load purpose.
|
|
console.log(`Test complete. SKU count: ${data.skuCount}`);
|
|
}
|
|
|
|
// ---------------- Summary ----------------
|
|
export function handleSummary(data) {
|
|
return {
|
|
stdout: JSON.stringify(
|
|
{
|
|
metrics: {
|
|
mutations_avg: data.metrics.cart_add_item_duration?.avg,
|
|
mutations_p95: data.metrics.cart_add_item_duration?.p(95),
|
|
fetch_p95: data.metrics.cart_fetch_duration?.p(95),
|
|
checkout_count: data.metrics.cart_checkout_calls?.count,
|
|
},
|
|
checks: data.root_checks,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
};
|
|
}
|