HTTP Requests
ctx.http is a thin wrapper around ky — a popular, well-tested HTTP client for Node.js. All ky APIs are available (retry, timeout, hooks, searchParams, json, etc.). Glubean adds automatic tracing, metrics, and schema validation on top.
Three differences from vanilla ky:
- Leading slashes work with
prefixUrl—ctx.http.get("/users")just works. Ky normally rejects this ; Glubean strips the leading/automatically. - Non-2xx responses don’t throw — ky defaults to
throwHttpErrors: true, but in a verification context you need to assert on 4xx/5xx responses, not catch exceptions. Glubean setsthrowHttpErrors: falseso you always get a response object back. Usectx.expect(res).toHaveStatus(404)to verify error status codes. - Empty
searchParamsdon’t pollute URLs — passingsearchParams: {}in ky appends a bare?to the URL. Glubean removes empty searchParams automatically so your traces stay clean.
Every request automatically produces traces and metrics — no extra setup needed.
Tired of repeating URLs and headers? Use
configure()to create a shared HTTP client withprefixUrl, auth headers, and environment-specific settings — then just writectx.http.get("/users")in your tests.
Basic usage
import { test } from "@glubean/sdk";
export const getUser = test("get-user", async (ctx) => {
const baseUrl = ctx.vars.require("BASE_URL");
const users = await ctx.http.get(`${baseUrl}/users`).json();
const created = await ctx.http.post(`${baseUrl}/users`, {
json: { name: "Alice" },
}).json();
});Create a scoped client
When multiple requests share a base URL or headers, use .extend():
export const userFlow = test("user-flow", async (ctx) => {
const api = ctx.http.extend({
prefixUrl: ctx.vars.require("BASE_URL"),
headers: {
Authorization: `Bearer ${ctx.secrets.require("TOKEN")}`,
},
});
await api.get("users/1").json();
await api.get("users/1/settings").json();
});Request options
| Option | Type | Description |
|---|---|---|
json | object | JSON body (auto-serialized) |
body | FormData | string | Raw body |
searchParams | Record<string, string> | Query string |
headers | Record<string, string> | Request headers |
timeout | number | false | Timeout in ms (default 10000, false to disable) |
retry | number | object | Retry count or { limit, statusCodes, methods } |
throwHttpErrors | boolean | Throw on 4xx/5xx (default false) |
schema | HttpSchemaOptions | Per-call Zod validation for request/response body, query, and headers. See Inline schema validation below. |
Inline schema validation
Pass schema on any call to validate request and response payloads against Zod (or any safeParse / parse-compatible) schemas. Violations are recorded as assertion failures without disrupting the rest of the test.
import { z } from "zod";
const BodySchema = z.object({ name: z.string(), email: z.string().email() });
const ResponseSchema = z.object({ id: z.string(), createdAt: z.string() });
await ctx.http.post(url, {
json: { name: "Alice", email: "alice@example.com" },
headers: { "X-Tenant-Id": "t1" },
schema: {
request: BodySchema,
response: ResponseSchema,
query: z.object({ version: z.string() }),
requestHeaders: z.object({ "X-Tenant-Id": z.string() }),
responseHeaders: z.object({ "content-type": z.string() }),
},
}).json();Per-entry options:
| Key | Validates |
|---|---|
request | The json body before sending |
response | The parsed body when .json() is called |
query | The searchParams before sending |
requestHeaders | The per-call headers (not merged client-level defaults) |
responseHeaders | The response headers on the final attempt |
Every entry accepts either a bare schema or { schema, severity: "error" | "warn" | "fatal" }. Default severity is "error".
schema: {
query: { schema: QuerySchema, severity: "warn" }, // logs but doesn't fail
}Headers are normalized to Record<string, string> before validation — Headers instances and string[][] forms both work.
For structured, file-level API specifications covering every case of an endpoint (instead of inline per-call validation), use contract.http() instead.
Response methods
const res = await ctx.http.get(url);
await res.json<T>(); // Parse JSON body
await res.text(); // Plain text
await res.blob(); // Binary blob
await res.arrayBuffer(); // Raw bufferRetries
For flaky environments, you can retry selected status codes:
await ctx.http.get("https://api.example.com/status", {
retry: {
limit: 3,
statusCodes: [429, 502, 503, 504],
},
});Auto-instrumentation
Every ctx.http request automatically records:
- An API trace via
ctx.trace()(method, URL, status, duration) - A metric
http_duration_msviactx.metric()(with method and path tags)
No manual instrumentation needed. Results appear in the Result Viewer and Cloud dashboard.