From 80d684db64abbf6997ecfd634e9f050048b1553c Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Sat, 15 Aug 2026 08:55:58 -0400 Subject: [PATCH] docs: complete public API documentation --- package-lock.json | 4 ++-- package.json | 2 +- src/client.ts | 35 +++++++++++++++++++++++++++++++++++ src/cookies.ts | 7 +++++++ src/request.ts | 14 ++++++++++++++ src/types.ts | 14 ++++++++++++++ 6 files changed, 73 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f81ab47..d858a95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@askrjs/testing", - "version": "0.0.6", + "version": "0.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@askrjs/testing", - "version": "0.0.6", + "version": "0.0.7", "license": "Apache-2.0", "dependencies": { "tough-cookie": "^6.0.2" diff --git a/package.json b/package.json index 83ea8e6..4a02ce6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@askrjs/testing", - "version": "0.0.6", + "version": "0.0.7", "description": "Transport-neutral request injection and HTTP testing utilities for Askr applications.", "keywords": [ "askr", diff --git a/src/client.ts b/src/client.ts index 1f2489a..24cd173 100644 --- a/src/client.ts +++ b/src/client.ts @@ -9,6 +9,7 @@ import type { TestCookieJar, } from "./types"; +/** A reusable HTTP client that injects requests into a target and follows redirects. */ export interface TestClient { readonly cookies?: TestCookieJar; request(path: string | URL, options?: InjectOptions): Promise; @@ -86,6 +87,20 @@ async function run( } } +/** + * Create a {@link TestClient} bound to a target for repeated request injection. + * + * The returned client applies shared defaults (base URL, headers, cookie jar, + * redirect behavior) to every request made through it, and follows redirects + * automatically unless `redirect` is overridden. + * + * @param target - The handler or {@link RequestTarget} to inject requests into. + * @param options - Default options applied to every request made by this client. + * @returns A {@link TestClient} with `request`, `get`, `post`, and other HTTP-method helpers. + * @example + * const client = createTestClient(app, { baseUrl: "https://example.com", cookies: true }); + * const response = await client.get("/users"); + */ export function createTestClient(target: Injectable, options: TestClientOptions = {}): TestClient { const jar = options.cookies === true ? createTestCookieJar() : options.cookies; const request = (path: string | URL, requestOptions: InjectOptions = {}) => { @@ -116,11 +131,31 @@ export function createTestClient(target: Injectable, options: TestClientOptions } as TestClient; } +/** + * Inject a single request into a target and return the resulting response, + * following redirects up to `maxRedirects` hops. + * + * @param target - The handler or {@link RequestTarget} to inject the request into. + * @param request - An existing `Request` to dispatch as-is. + * @param options - Only `maxRedirects` is honored when a `Request` is passed directly. + * @returns The final `Response` after any redirects have been followed. + */ export function inject( target: Injectable, request: Request, options?: Pick, ): Promise; +/** + * Inject a request built from a path/URL and options into a target and return + * the resulting response, following redirects up to `maxRedirects` hops. + * + * @param target - The handler or {@link RequestTarget} to inject the request into. + * @param input - The request path or URL, resolved against `options.baseUrl`. + * @param options - Request options such as method, headers, query, and body. + * @returns The final `Response` after any redirects have been followed. + * @example + * const response = await inject(app, "/users", { method: "GET" }); + */ export function inject( target: Injectable, input: string | URL, diff --git a/src/cookies.ts b/src/cookies.ts index a5d5843..0ff20a0 100644 --- a/src/cookies.ts +++ b/src/cookies.ts @@ -1,6 +1,13 @@ import { CookieJar } from "tough-cookie"; import type { TestCookie, TestCookieJar } from "./types"; +/** + * Create an in-memory {@link TestCookieJar} backed by `tough-cookie`, suitable + * for use as the `cookies` option of a {@link TestClient}. + * + * @returns A cookie jar that persists cookies across injected requests and + * enforces standard cookie-prefix security. + */ export function createTestCookieJar(): TestCookieJar { const jar = new CookieJar(undefined, { prefixSecurity: "strict" }); const api: TestCookieJar = { diff --git a/src/request.ts b/src/request.ts index f4c124b..a578060 100644 --- a/src/request.ts +++ b/src/request.ts @@ -21,6 +21,20 @@ function bodyModes(options: InjectOptions): string[] { ); } +/** + * Build a `Request` for testing from a path or URL and a set of options. + * + * Resolves `input` against `options.baseUrl` (defaulting to `https://askr.test/`), + * appends any `query` parameters, and serializes at most one of `body`, `json`, + * or `form` into the request body, setting an appropriate `content-type` header + * when one isn't already present. Throws a `TypeError` if more than one body + * mode is supplied, if `json` is `undefined`, or if a `GET`/`HEAD` request is + * given a body. + * + * @param input - The request path or URL. + * @param options - Request options such as method, headers, query, and body. + * @returns A `Request` ready to be dispatched to a test target. + */ export function createTestRequest(input: string | URL, options: InjectOptions = {}): Request { const modes = bodyModes(options); if (modes.length > 1) throw new TypeError(`Request body modes conflict: ${modes.join(", ")}`); diff --git a/src/types.ts b/src/types.ts index 3b4cbf5..a941153 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,17 +1,24 @@ +/** A target that can receive an injected request directly via a `fetch`-style method. */ export interface RequestTarget { fetch(request: Request): Response | Promise; } +/** A function that handles a `Request` and produces a `Response`, synchronously or asynchronously. */ export type RequestHandler = (request: Request) => Response | Promise; +/** Anything that can receive an injected test request: a {@link RequestTarget} or a {@link RequestHandler}. */ export type Injectable = RequestTarget | RequestHandler; +/** A single query string value, coerced to `string` when serialized. */ export type QueryValue = string | number | boolean; +/** Query string parameters, accepted as `URLSearchParams`, an iterable of entries, or a plain record. */ export type Query = | URLSearchParams | Iterable | Record; +/** A single form field value, coerced to `string` when serialized. */ export type FormValue = string | number | boolean; +/** URL-encoded form body data, accepted as `URLSearchParams`, an iterable of entries, or a plain record. */ export type Form = | URLSearchParams | Iterable @@ -30,10 +37,12 @@ type RequestOptionsBase = Omit & { maxRedirects?: number; }; +/** Options for a body-less `GET` or `HEAD` request. */ export type GetHeadOptions = RequestOptionsBase & { method?: "GET" | "HEAD" | "get" | "head"; } & NoBody; +/** Options for a request that may carry a body, restricted to methods that support one. */ export type BodyRequestOptions = RequestOptionsBase & { method: | "POST" @@ -48,8 +57,10 @@ export type BodyRequestOptions = RequestOptionsBase & { | "options"; } & BodyMode; +/** Options accepted when injecting a request, covering both body-less and body-carrying methods. */ export type InjectOptions = GetHeadOptions | BodyRequestOptions; +/** A cookie as read back from a {@link TestCookieJar}. */ export interface TestCookie { name: string; value: string; @@ -61,15 +72,18 @@ export interface TestCookie { sameSite?: "strict" | "lax" | "none"; } +/** A cookie jar used to persist and replay cookies across injected requests. */ export interface TestCookieJar { setCookie(cookie: string, url: string | URL): Promise; getCookies(url: string | URL): Promise; clear(): Promise; } +/** Options for constructing a {@link TestClient}. */ export interface TestClientOptions { baseUrl?: string | URL; headers?: HeadersInit; + /** Enable an automatically managed cookie jar (`true`), or supply an existing {@link TestCookieJar}. */ cookies?: true | TestCookieJar; redirect?: RequestRedirect; maxRedirects?: number;