itdoc
Version:
Test-driven documentation for RESTful services
299 lines (286 loc) • 10.4 kB
text/typescript
declare enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
PATCH = "PATCH",
HEAD = "HEAD",
OPTIONS = "OPTIONS",
TRACE = "TRACE",
CONNECT = "CONNECT"
}
declare enum HttpStatus {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLY_HINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
MULTI_STATUS = 207,
ALREADY_REPORTED = 208,
IM_USED = 226,
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
USE_PROXY = 305,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
IM_A_TEAPOT = 418,
MISDIRECTED_REQUEST = 421,
UNPROCESSABLE_ENTITY = 422,
LOCKED = 423,
FAILED_DEPENDENCY = 424,
TOO_EARLY = 425,
UPGRADE_REQUIRED = 426,
PRECONDITION_REQUIRED = 428,
TOO_MANY_REQUESTS = 429,
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
VARIANT_ALSO_NEGOTIATES = 506,
INSUFFICIENT_STORAGE = 507,
LOOP_DETECTED = 508,
NOT_EXTENDED = 510,
NETWORK_AUTHENTICATION_REQUIRED = 511
}
type FIELD_TYPES = string | number | boolean | object | null | Record<string, string | number | boolean | object | null | DSLField> | FIELD_TYPES[];
/**
* DSL Field interface
* - example can be a value or value validation function.
*/
interface DSLField<T extends FIELD_TYPES = FIELD_TYPES> {
readonly description: string;
readonly example: T | ((value: T) => void);
readonly required: boolean;
}
interface DSLRequestFile {
readonly description: string;
readonly file: {
path?: string;
buffer?: Buffer;
stream?: NodeJS.ReadableStream;
};
readonly opts: {
contentType: string;
filename?: string;
};
}
/**
* DSL Helper Functions
* - DSL Field creation function
* @param {string} description Field description to be displayed in documentation
* @param {T | (value: T) => void} example Example value, or a validator that receives the value
* @param {boolean} required Whether the field is required
* @returns {DSLField<FIELD_TYPES>} DSL Field interface
*/
declare function field<T extends FIELD_TYPES>(description: string, example: T | ((value: T) => void), required?: boolean): DSLField<FIELD_TYPES>;
type PATH_PARAM_TYPES = string | number;
type QUERY_PARAM_TYPES = string | number | boolean;
/**
* Defines configuration values set for each test case.
*/
interface TestCaseConfig {
/**
* Options for API documentation
*/
apiOptions?: ApiDocOptions;
pathParams?: Record<string, DSLField<PATH_PARAM_TYPES> | PATH_PARAM_TYPES>;
queryParams?: Record<string, DSLField<QUERY_PARAM_TYPES> | QUERY_PARAM_TYPES>;
requestBody?: Record<string, DSLField | FIELD_TYPES>;
requestHeaders?: Record<string, DSLField<string> | string>;
requestFile?: DSLRequestFile;
expectedStatus?: HttpStatus | number;
expectedResponseBody?: Record<string, DSLField>;
expectedResponseHeaders?: Record<string, DSLField<string> | string>;
prettyPrint?: boolean;
}
/**
* Abstract class containing common settings for builder classes under test-builders.
* @see https://github.com/do-pa/itdoc/issues/10
*/
declare abstract class AbstractTestBuilder {
protected readonly config: TestCaseConfig;
protected readonly method: HttpMethod;
protected readonly url: string;
protected readonly app: any;
constructor(defaults: TestCaseConfig | undefined, method: HttpMethod, url: string, app: any);
}
/**
* Test result interface
*
* This interface contains information for capturing API test results.
* @property {HttpMethod} method - HTTP method (e.g., GET, POST, etc.).
* @property {string} url - Request URL.
* @property {ApiDocOptions} options - API documentation generation options.
* @property {object} request - Request-related information.
* @property {unknown} [request.body] - Request body (optional).
* @property {Record<string, string | unknown>} [request.headers] - Request headers (optional).
* @property {Record<string, string | unknown>} [request.queryParams] - URL query parameters (optional).
* @property {Record<string, string | unknown>} [request.pathParams] - URL path parameters (optional).
* @property {object} response - Response-related information.
* @property {number} response.status - HTTP response status code.
* @property {unknown} [response.body] - Response body (optional).
* @property {Record<string, string | unknown>} [response.headers] - Response headers (optional).
* @property {string} [testSuiteDescription] - Test context description. For example,
* the "test context" part in itDoc("test context", () => { ... }).
*/
interface TestResult {
method: HttpMethod;
url: string;
options: ApiDocOptions;
request: {
file?: DSLRequestFile;
body?: unknown;
headers?: Record<string, string | unknown>;
queryParams?: Record<string, string | unknown>;
pathParams?: Record<string, string | unknown>;
};
response: {
status: number;
body?: unknown;
headers?: Record<string, string | unknown>;
};
testSuiteDescription?: string;
}
/**
* Builder class for setting result values to validate API responses.
*/
declare class ResponseBuilder extends AbstractTestBuilder {
status(status: HttpStatus | number): this;
header(headers: Record<string, string | DSLField<string>>): this;
body(body: Record<string, DSLField>): this;
private runTest;
private prepareHeadersForCollector;
then<TResult1 = TestResult, TResult2 = never>(resolve?: ((value: TestResult) => TResult1 | PromiseLike<TResult1>) | null, reject?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
}
interface FileDescriptor {
readonly path?: string;
readonly buffer?: Buffer;
readonly stream?: NodeJS.ReadableStream;
readonly filename?: string;
readonly contentType?: string;
}
/**
* Builder class for setting API request information.
*/
declare class RequestBuilder extends AbstractTestBuilder {
/**
* Sets headers to be used in requests. Header names are normalized to lowercase.
* @param {Record<string, DSLField<string>>} headers Headers to be used in requests
* @returns {this} Request builder instance
*/
header(headers: Record<string, DSLField<string>>): this;
/**
* Sets the request body as a raw file (NOT multipart/form-data).
*
* Two invocation styles are supported:
* 1. Shorthand – `req().file("description", { path | buffer | stream, filename?, contentType? })`
* 2. Advanced – pass a custom {@link DSLRequestFile} object (legacy support).
*
* The request is mutually exclusive with {@link body()}.
*/
file(description: string, descriptor: FileDescriptor): this;
file(requestFile: DSLRequestFile): this;
private normalizeFileArguments;
private isReadableStream;
private applyFile;
/**
* Sets the request body.
* @param {Record<string, DSLField<FIELD_TYPES> | FIELD_TYPES>} body Request body
* @returns {this} Request builder instance
*/
body(body: Record<string, DSLField<FIELD_TYPES> | FIELD_TYPES>): this;
/**
* Sets query parameters to be used in requests.
* @param {Record<string, any>} params Query parameters to be used in requests
* @returns {this} Request builder instance
*/
queryParam(params: Record<string, any>): this;
/**
* Sets path parameters to be used in requests.
* @param {Record<string, any>} params Path parameters to be used in requests
* @returns {this} Request builder instance
*/
pathParam(params: Record<string, any>): this;
/**
* Creates a ResponseBuilder instance.
* @returns {ResponseBuilder} Response builder instance
*/
res(): ResponseBuilder;
}
/**
* The RootBuilder class is the starting point for API testing.
*/
declare class RootBuilder extends AbstractTestBuilder {
/**
* Sets the prettyPrint configuration value to true.
*/
prettyPrint(): this;
req(): RequestBuilder;
}
/**
* Option interface to pass to Describe API
*/
declare class ItdocBuilderEntry {
readonly method: HttpMethod;
readonly url: string;
readonly options: ApiDocOptions;
readonly app: unknown;
constructor(method: HttpMethod, url: string, options: ApiDocOptions, app: unknown);
test(): RootBuilder;
}
/**
* Option interface to pass to Describe API
* @param summary One-line API summary
* @param tag API tag
* @param description Detailed API description
*/
interface ApiDocOptions {
summary?: string;
tag?: string;
description?: string;
defaults?: TestCaseConfig;
}
/**
* Describe function for API specification
* @param {HttpMethod} method HTTP method
* @param {string} url API URL
* @param {ApiDocOptions} options API documentation options
* @param {unknown} app Express app instance (used for supertest creation)
* @param {Function} callback API test function
* @returns {void}
* @throws {Error} When required parameters are missing
*/
declare const describeAPI: (method: HttpMethod, url: string, options: ApiDocOptions, app: unknown, callback: (apiDoc: ItdocBuilderEntry) => void) => void;
declare const itDoc: (description: string, testFn: () => Promise<void> | Promise<TestResult> | void | TestResult) => void;
export { type ApiDocOptions, HttpMethod, HttpStatus, describeAPI, field, itDoc };