// 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; }