Skip to Content
SDK & PluginsContract HTTP

Contract HTTP

Use contract.http() to declare endpoint behavior as named cases. Each case becomes a runnable test — no test() wrapper needed.

When to use

  • You want to define what an API should do before implementing it
  • You want structured, scannable specs that tools can extract without running code
  • You want one declaration to cover success, error, and edge cases for a single endpoint

Basic contract

import { contract, configure } from "@glubean/sdk"; const { api } = configure({ http: { prefixUrl: "{{BASE_URL}}" }, }); export const createUser = contract.http("create-user", { endpoint: "POST /users", feature: "User Registration", description: "Create a new user account", client: api, cases: { success: { description: "Valid registration creates user and returns profile", body: { email: "test@example.com", password: "secure123" }, expect: { status: 201 }, }, duplicateEmail: { description: "Already registered email is rejected", body: { email: "existing@example.com", password: "secure123" }, expect: { status: 409 }, }, missingPassword: { description: "Missing required field returns validation error", body: { email: "test@example.com" }, expect: { status: 400 }, }, }, });

Each case key (success, duplicateEmail, missingPassword) becomes a test with ID create-user.success, etc.

Contract-level fields

FieldTypeRequiredDescription
endpointstringYesHTTP method + path, e.g. "POST /users"
clientHTTP clientNoDefault client for all cases (from configure())
featurestringNoGroups contracts in projection output (e.g. "User Registration")
descriptionstringNoWhat this endpoint does, in business language
requestZod schemaNoRequest body schema (documentation, not enforced)
tagsstring[]NoInherited by all cases

Case fields

FieldTypeRequiredDescription
descriptionstringYesWhy this case exists — business behavior, not HTTP details
expectobjectYes{ status: number, schema?: ZodSchema }
bodyobject / fnNoRequest body. Can be a function of state: (state) => ({...})
headersobject / fnNoRequest headers. Can be a function of state
paramsobjectNoURL path parameters (e.g. { id: "123" } for /users/:id)
queryobjectNoQuery string parameters
clientHTTP clientNoOverride contract-level client (useful for auth testing)
setupfnNoRun before request: async (ctx) => state
teardownfnNoRun after request (always, even on failure): async (ctx, state) => void
verifyfnNoCustom assertions after response: async (ctx, response) => void
deferredstringNoMark case as not yet runnable, with reason
requiresstringNo"headless" (default), "browser", or "out-of-band"
defaultRunstringNo"always" (default) or "opt-in"
tagsstring[]NoCase-specific tags (merged with contract-level tags)

Schema validation

Use expect.schema with a Zod schema to validate response shape:

import { z } from "zod"; const UserSchema = z.object({ id: z.string(), email: z.string().email(), createdAt: z.string().datetime(), }); export const getUser = contract.http("get-user", { endpoint: "GET /users/:id", client: api, cases: { found: { description: "Returns full user profile by ID", params: { id: "user-123" }, expect: { status: 200, schema: UserSchema }, }, }, });

Custom verification

Use verify for assertions beyond status and schema:

cases: { success: { description: "Created user has correct email", body: { email: "new@example.com", password: "secure123" }, expect: { status: 201 }, verify: async (ctx, res) => { const body = await res.json(); ctx.expect(body.email).toBe("new@example.com"); ctx.expect(body.id).toBeDefined(); }, }, }

Deferred cases

Mark cases that can’t run yet:

cases: { rateLimit: { description: "Excessive requests are throttled", deferred: "Rate limiting not deployed yet", body: { email: "spam@example.com", password: "x" }, expect: { status: 429 }, }, }

Deferred cases are skipped during execution and show as in projection output.

Per-case auth

Use different clients to test auth boundaries:

const { api, adminApi, unauthenticated } = configure({ http: { prefixUrl: "{{BASE_URL}}" }, }); export const deleteUser = contract.http("delete-user", { endpoint: "DELETE /users/:id", client: adminApi, cases: { success: { description: "Admin can delete a user", params: { id: "user-123" }, expect: { status: 204 }, }, forbidden: { description: "Regular user cannot delete others", client: api, params: { id: "user-123" }, expect: { status: 403 }, }, unauthenticated: { description: "Anonymous access is rejected", client: unauthenticated, params: { id: "user-123" }, expect: { status: 401 }, }, }, });

Projection

Run glubean contracts to see all contracts as a human-readable report:

glubean contracts # markdown outline (default) glubean contracts --format json # machine-readable JSON

The output groups contracts by feature and lists cases with descriptions. Technical descriptions trigger lint warnings.

Description guidelines

Case descriptions should use business language:

BadGood
POST creates a userValid registration creates user and returns profile
Returns 409Already registered email is rejected
Validates request bodyMissing required field returns validation error

glubean contracts warns about descriptions that start with HTTP methods, contain status codes, or use jargon like “endpoint”, “payload”, or “request body”.

Last updated on