template-monorepo/test/k6/lib/http.js

121 lines
4.0 KiB
JavaScript

// HTTP helpers — every request goes through these so checks/envelope handling
// is consistent across smoke and journey scripts.
import http from 'k6/http';
import { check, fail } from 'k6';
import { cfg, SUCCESS_CODE } from './config.js';
const JSON_HEADERS = { 'Content-Type': 'application/json', Accept: 'application/json' };
function url(path) {
if (path.startsWith('http://') || path.startsWith('https://')) return path;
return cfg.baseUrl + path;
}
function mergeHeaders(extra) {
return Object.assign({}, JSON_HEADERS, extra || {});
}
export function withBearer(token, extra) {
return mergeHeaders(Object.assign({ Authorization: `Bearer ${token}` }, extra || {}));
}
export function get(path, headers, params) {
return http.get(url(path), Object.assign({ headers: mergeHeaders(headers) }, params || {}));
}
export function post(path, body, headers, params) {
return http.post(
url(path),
body == null ? null : JSON.stringify(body),
Object.assign({ headers: mergeHeaders(headers) }, params || {}),
);
}
export function put(path, body, headers, params) {
return http.put(
url(path),
body == null ? null : JSON.stringify(body),
Object.assign({ headers: mergeHeaders(headers) }, params || {}),
);
}
export function patch(path, body, headers, params) {
return http.patch(
url(path),
body == null ? null : JSON.stringify(body),
Object.assign({ headers: mergeHeaders(headers) }, params || {}),
);
}
export function del(path, body, headers, params) {
return http.del(
url(path),
body == null ? null : JSON.stringify(body),
Object.assign({ headers: mergeHeaders(headers) }, params || {}),
);
}
// safeJson parses res.body; returns null when body is empty/not JSON.
export function safeJson(res) {
if (!res || !res.body || res.body.length === 0) return null;
try {
return JSON.parse(res.body);
} catch (_) {
return null;
}
}
// checkEnvelope verifies the standard CloudEP success envelope.
// { code: 102000, message: "SUCCESS", data: ... }
// Returns the parsed body so callers can keep chaining.
export function checkEnvelope(res, label, expectedStatus = 200, expectedCode = SUCCESS_CODE) {
const body = safeJson(res);
const ok = check(res, {
[`${label}: status ${expectedStatus}`]: (r) => r.status === expectedStatus,
[`${label}: code ${expectedCode}`]: () => body && body.code === expectedCode,
});
if (!ok) {
// surface real payload so failures are actionable in `make k6-*` output
fail(`${label} failed: status=${res.status} body=${res.body}`);
}
return body;
}
// checkError expects a non-2xx response and a business error code.
// expectedBiz is the 8-digit numeric SSCCCDDD code.
export function checkError(res, label, expectedStatus, expectedBiz) {
const body = safeJson(res);
const ok = check(res, {
[`${label}: status ${expectedStatus}`]: (r) => r.status === expectedStatus,
[`${label}: code ${expectedBiz}`]: () => body && body.code === expectedBiz,
});
if (!ok) {
fail(`${label} expected error ${expectedBiz} got status=${res.status} body=${res.body}`);
}
return body;
}
// checkErrorOneOf accepts any of several (status, code) pairs. Use for
// endpoints whose error path depends on environment wiring (e.g.
// /auth/login may legitimately return either:
// 401 + 28501000 (invalid credentials, OAuth wired)
// 502 + 28802000 (zitadel request failed, OAuth not wired or password
// grant not enabled — true of modern ZITADEL v2 by default)
// pairs: array of [status, bizCode] tuples.
export function checkErrorOneOf(res, label, pairs) {
const body = safeJson(res);
const matched = pairs.find(
([s, c]) => res.status === s && body && body.code === c,
);
const labelStr = pairs.map(([s, c]) => `${s}+${c}`).join(' | ');
const ok = check(res, {
[`${label}: one of {${labelStr}}`]: () => Boolean(matched),
});
if (!ok) {
fail(
`${label} expected one of [${labelStr}] got status=${res.status} body=${res.body}`,
);
}
return body;
}