mockttp-mvs
Version:
Mock HTTP server for testing HTTP clients and stubbing webservices
663 lines (662 loc) • 26.2 kB
TypeScript
/// <reference types="node" />
import type * as net from 'net';
import { Readable } from 'stream';
import { Headers, CompletedRequest, CompletedBody, Explainable, RawHeaders } from "../../types";
import { MaybePromise, Replace } from '../../util/type-utils';
import { Serializable, ClientServerChannel, SerializedProxyConfig } from "../../serialization/serialization";
import { ProxyConfig } from '../proxy-config';
export interface RequestHandlerDefinition extends Explainable, Serializable {
type: keyof typeof HandlerDefinitionLookup;
}
export declare type SerializedBuffer = {
type: 'Buffer';
data: number[];
};
/**
* Can be returned from callbacks to override parts of a request.
*
* All fields are optional, and omitted values will default to the original
* request value.
*/
export interface CallbackRequestResult {
/**
* A replacement HTTP method, capitalized.
*/
method?: string;
/**
* The full URL to send the request to. If set, this will redirect
* the request and automatically update the Host header accordingly,
* unless you also provide a `headers` value that includes a Host
* header, in which case that will take used as-is.
*/
url?: string;
/**
* The replacement HTTP headers, as an object of string keys and either
* single string or array of string values.
*/
headers?: Headers;
/**
* A string or buffer, which replaces the request body if set. This will
* be automatically content-encoded to match the Content-Encoding defined
* in your request headers.
*
* If this is set, the Content-Length header will be automatically updated
* accordingly to match, unless you also provide a `headers` value that
* includes a Content-Length header, in which case that will take used
* as-is.
*
* You should only return one body field: either `body`, `rawBody` or
* `json`.
*/
body?: string | Buffer | Uint8Array;
/**
* A buffer, which replaces the request body if set, which is sent exactly
* as is, and is not automatically encoded.
*
* If this is set, the Content-Length header will be automatically updated
* accordingly to match, unless you also provide a `headers` value that
* includes a Content-Length header, in which case that will take used
* as-is.
*
* You should only return one body field: either `body`, `rawBody` or
* `json`.
*/
rawBody?: Buffer | Uint8Array;
/**
* A JSON value, which will be stringified and send as a JSON-encoded
* request body. This will be automatically content-encoded to match
* the Content-Encoding defined in your request headers.
*
* If this is set, the Content-Length header will be automatically updated
* accordingly to match, unless you also provide a `headers` value that
* includes a Content-Length header, in which case that will take used
* as-is.
*
* You should only return one body field: either `body`, `rawBody` or
* `json`.
*/
json?: any;
/**
* A response: either a response object defining the fields of a response
* or the string 'close' to immediately close the connection.
*
* See {@link CallbackResponseMessageResult} for the possible fields that can
* be set to define the response.
*
* If set, the request will not be forwarded at all, and this will be used
* as the response to immediately return to the client (or for 'close', this
* will immediately close the connection to the client).
*/
response?: CallbackResponseResult;
}
export declare type CallbackResponseResult = CallbackResponseMessageResult | 'close' | 'reset';
/**
* Can be returned from callbacks to define parts of a response, or
* override parts when given an existing repsonse.
*
* All fields are optional, and omitted values will default to the original
* response value or a default value.
*/
export interface CallbackResponseMessageResult {
/**
* The response status code as a number.
*
* Defaults to 200 if not set.
*/
statusCode?: number;
/**
* Supported only for backward compatibility.
*
* @deprecated Use statusCode instead.
*/
status?: number;
/**
* The response status message, as a string. This is ignored for
* HTTP/2 responses.
*
* Defaults to the default status message for the status code if not set.
*/
statusMessage?: string;
/**
* The replacement HTTP headers, as an object of string keys and either
* single string or array of string values.
*
* Defaults to a minimum set of standard required headers if not set.
*/
headers?: Headers;
/**
* A string or buffer, which replaces the response body if set. This will
* be automatically encoded to match the Content-Encoding defined in your
* response headers.
*
* If this is set, the Content-Length header will be automatically updated
* accordingly to match, unless you also provide a `headers` value that
* includes a Content-Length header, in which case that will take used
* as-is.
*
* Defaults to empty.
*
* You should only return one body field: either `body`, `rawBody` or
* `json`.
*/
body?: string | Buffer | Uint8Array;
/**
* A buffer, which replaces the response body if set, which is sent exactly
* as is, and is not automatically encoded.
*
* If this is set, the Content-Length header will be automatically updated
* accordingly to match, unless you also provide a `headers` value that
* includes a Content-Length header, in which case that will take used
* as-is.
*
* You should only return one body field: either `body`, `rawBody` or
* `json`.
*/
rawBody?: Buffer | Uint8Array;
/**
* A JSON value, which will be stringified and send as a JSON-encoded
* request body. This will be automatically content-encoded to match the
* Content-Encoding defined in your response headers.
*
* If this is set, the Content-Length header will be automatically updated
* accordingly to match, unless you also provide a `headers` value that
* includes a Content-Length header, in which case that will take used
* as-is.
*
* You should only return one body field: either `body`, `rawBody` or
* `json`.
*/
json?: any;
}
export declare class SimpleHandlerDefinition extends Serializable implements RequestHandlerDefinition {
status: number;
statusMessage?: string | undefined;
data?: string | Uint8Array | Buffer | SerializedBuffer | undefined;
headers?: Headers | undefined;
readonly type = "simple";
constructor(status: number, statusMessage?: string | undefined, data?: string | Uint8Array | Buffer | SerializedBuffer | undefined, headers?: Headers | undefined);
explain(): string;
}
/**
* @internal
*/
export interface SerializedCallbackHandlerData {
type: string;
name?: string;
version?: number;
}
/**
* @internal
*/
export interface CallbackRequestMessage {
args: [
Replace<CompletedRequest, {
body: string;
}> | CompletedRequest
];
}
export declare class CallbackHandlerDefinition extends Serializable implements RequestHandlerDefinition {
callback: (request: CompletedRequest) => MaybePromise<CallbackResponseResult>;
readonly type = "callback";
constructor(callback: (request: CompletedRequest) => MaybePromise<CallbackResponseResult>);
explain(): string;
/**
* @internal
*/
serialize(channel: ClientServerChannel): SerializedCallbackHandlerData;
}
/**
* @internal
*/
export interface SerializedStreamHandlerData {
type: string;
status: number;
headers?: Headers;
}
export declare class StreamHandlerDefinition extends Serializable implements RequestHandlerDefinition {
status: number;
stream: Readable & {
done?: true;
};
headers?: Headers | undefined;
readonly type = "stream";
constructor(status: number, stream: Readable & {
done?: true;
}, headers?: Headers | undefined);
explain(): string;
/**
* @internal
*/
serialize(channel: ClientServerChannel): SerializedStreamHandlerData;
}
export declare class FileHandlerDefinition extends Serializable implements RequestHandlerDefinition {
status: number;
statusMessage: string | undefined;
filePath: string;
headers?: Headers | undefined;
readonly type = "file";
constructor(status: number, statusMessage: string | undefined, filePath: string, headers?: Headers | undefined);
explain(): string;
}
export interface PassThroughResponse {
id: string;
statusCode: number;
statusMessage?: string;
headers: Headers;
rawHeaders: RawHeaders;
body: CompletedBody;
}
export interface ForwardingOptions {
targetHost: string;
updateHostHeader?: true | false | string;
}
export interface PassThroughLookupOptions {
/**
* The maximum time to cache a DNS response. Up to this limit,
* responses will be cached according to their own TTL. Defaults
* to Infinity.
*/
maxTtl?: number;
/**
* How long to cache a DNS ENODATA or ENOTFOUND response. Defaults
* to 0.15.
*/
errorTtl?: number;
/**
* The primary servers to use. DNS queries will be resolved against
* these servers first. If no data is available, queries will fall
* back to dns.lookup, and use the OS's default DNS servers.
*
* This defaults to dns.getServers().
*/
servers?: string[];
}
export interface PassThroughHandlerOptions {
/**
* The forwarding configuration for the passthrough rule.
* This generally shouldn't be used explicitly unless you're
* building rule data by hand. Instead, call `thenPassThrough`
* to send data directly or `thenForwardTo` with options to
* configure traffic forwarding.
*/
forwarding?: ForwardingOptions;
/**
* A list of hostnames for which server certificate and TLS version errors
* should be ignored (none, by default).
*
* If set to 'true', HTTPS errors will be ignored for all hosts. WARNING:
* Use this at your own risk. Setting this to `true` can open your
* application to MITM attacks and should never be used over any network
* that is not completed trusted end-to-end.
*/
ignoreHostHttpsErrors?: string[] | boolean;
/**
* An array of additional certificates, which should be trusted as certificate
* authorities for upstream hosts, in addition to Node.js's built-in certificate
* authorities.
*
* Each certificate should be an object with either a `cert` key and a string
* or buffer value containing the PEM certificate, or a `certPath` key and a
* string value containing the local path to the PEM certificate.
*/
trustAdditionalCAs?: Array<{
cert: string | Buffer;
} | {
certPath: string;
}>;
/**
* A mapping of hosts to client certificates to use, in the form of
* `{ key, cert }` objects (none, by default)
*/
clientCertificateHostMap?: {
[host: string]: {
pfx: Buffer;
passphrase?: string;
};
};
/**
* Upstream proxy configuration: pass through requests via this proxy.
*
* If this is undefined, no proxy will be used. To configure a proxy
* provide either:
* - a ProxySettings object
* - a callback which will be called with an object containing the
* hostname, and must return a ProxySettings object or undefined.
* - an array of ProxySettings or callbacks. The array will be
* processed in order, and the first not-undefined ProxySettings
* found will be used.
*
* When using a remote client, this parameter or individual array
* values may be passed by reference, using the name of a rule
* parameter configured in the admin server.
*/
proxyConfig?: ProxyConfig;
/**
* Custom DNS options, to allow configuration of the resolver used
* when forwarding requests upstream. Passing any option switches
* from using node's default dns.lookup function to using the
* cacheable-lookup module, which will cache responses.
*/
lookupOptions?: PassThroughLookupOptions;
/**
* Whether to simulate connection errors back to the client.
*
* By default (in most cases - see below) when an upstream request fails
* outright a 502 "Bad Gateway" response is sent to the downstream client,
* explicitly indicating the failure and containing the error that caused
* the issue in the response body.
*
* Only in the case of upstream connection reset errors is a connection reset
* normally sent back downstream to existing clients (this behaviour exists
* for backward compatibility, and will change to match other error behaviour
* in a future version).
*
* When this option is set to `true`, low-level connection failures will
* always trigger a downstream connection close/reset, rather than a 502
* response.
*
* This includes DNS failures, TLS connection errors, TCP connection resets,
* etc (but not HTTP non-200 responses, which are still proxied as normal).
* This is less convenient for debugging in a testing environment or when
* using a proxy intentionally, but can be more accurate when trying to
* transparently proxy network traffic, errors and all.
*/
simulateConnectionErrors?: boolean;
/**
* A set of data to automatically transform a request. This includes properties
* to support many transformation common use cases.
*
* For advanced cases, a custom callback using beforeRequest can be used instead.
* Using this field however where possible is typically simpler, more declarative,
* and can be more performant. The two options are mutually exclusive: you cannot
* use both transformRequest and a beforeRequest callback.
*
* Only one transformation for each target (method, headers & body) can be
* specified. If more than one is specified then an error will be thrown when the
* rule is registered.
*/
transformRequest?: RequestTransform;
/**
* A set of data to automatically transform a response. This includes properties
* to support many transformation common use cases.
*
* For advanced cases, a custom callback using beforeResponse can be used instead.
* Using this field however where possible is typically simpler, more declarative,
* and can be more performant. The two options are mutually exclusive: you cannot
* use both transformResponse and a beforeResponse callback.
*
* Only one transformation for each target (status, headers & body) can be
* specified. If more than one is specified then an error will be thrown when the
* rule is registered.
*/
transformResponse?: ResponseTransform;
/**
* A callback that will be passed the full request before it is passed through,
* and which returns an object that defines how the the request content should
* be transformed before it's passed to the upstream server.
*
* The callback can return an object to define how the request should be changed.
* All fields on the object are optional, and returning undefined is equivalent
* to returning an empty object (transforming nothing).
*
* See {@link CallbackRequestResult} for the possible fields that can be set.
*/
beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult | void> | void;
/**
* A callback that will be passed the full response before it is passed through,
* and which returns a value that defines how the the response content should
* be transformed before it's returned to the client.
*
* The callback can either return an object to define how the response should be
* changed, or the string 'close' to immediately close the underlying connection.
*
* All fields on the object are optional, and returning undefined is equivalent
* to returning an empty object (transforming nothing).
*
* See {@link CallbackResponseMessageResult} for the possible fields that can be set.
*/
beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult | void> | void;
}
export interface RequestTransform {
/**
* A replacement HTTP method. Case insensitive.
*/
replaceMethod?: string;
/**
* A headers object which will be merged with the real request headers to add or
* replace values. Headers with undefined values will be removed.
*/
updateHeaders?: Headers;
/**
* A headers object which will completely replace the real request headers.
*/
replaceHeaders?: Headers;
/**
* A string or buffer that replaces the request body entirely.
*
* If this is specified, the upstream request will not wait for the original request
* body, so this may make responses faster than they would be otherwise given large
* request bodies or slow/streaming clients.
*/
replaceBody?: string | Uint8Array | Buffer;
/**
* The path to a file, which will be used to replace the request body entirely. The
* file will be re-read for each request, so the body will always reflect the latest
* file contents.
*
* If this is specified, the upstream request will not wait for the original request
* body, so this may make responses faster than they would be otherwise given large
* request bodies or slow/streaming clients.
*/
replaceBodyFromFile?: string;
/**
* A JSON object which will be merged with the real request body. Undefined values
* will be removed. Any requests which are received with an invalid JSON body that
* match this rule will fail.
*/
updateJsonBody?: {
[key: string]: any;
};
/**
* Perform a series of string match & replace operations on the request body.
*
* This parameter should be an array of pairs, which will be applied to the body
* decoded as a string like `body.replace(p1, p2)`, applied in the order provided.
* The first parameter can be either a string or RegExp to match, and the second
* must be a string to insert. The normal `str.replace` $ placeholders can be
* used in the second argument, so that e.g. $1 will insert the 1st matched group.
*/
matchReplaceBody?: Array<[string | RegExp, string]>;
}
export interface ResponseTransform {
/**
* A replacement response status code.
*/
replaceStatus?: number;
/**
* A headers object which will be merged with the real response headers to add or
* replace values. Headers with undefined values will be removed.
*/
updateHeaders?: Headers;
/**
* A headers object which will completely replace the real response headers.
*/
replaceHeaders?: Headers;
/**
* A string or buffer that replaces the response body entirely.
*
* If this is specified, the downstream response will not wait for the original response
* body, so this may make responses arrive faster than they would be otherwise given large
* response bodies or slow/streaming servers.
*/
replaceBody?: string | Uint8Array | Buffer;
/**
* The path to a file, which will be used to replace the response body entirely. The
* file will be re-read for each response, so the body will always reflect the latest
* file contents.
*
* If this is specified, the downstream response will not wait for the original response
* body, so this may make responses arrive faster than they would be otherwise given large
* response bodies or slow/streaming servers.
*/
replaceBodyFromFile?: string;
/**
* A JSON object which will be merged with the real response body. Undefined values
* will be removed. Any responses which are received with an invalid JSON body that
* match this rule will fail.
*/
updateJsonBody?: {
[key: string]: any;
};
/**
* Perform a series of string match & replace operations on the response body.
*
* This parameter should be an array of pairs, which will be applied to the body
* decoded as a string like `body.replace(p1, p2)`, applied in the order provided.
* The first parameter can be either a string or RegExp to match, and the second
* must be a string to insert. The normal `str.replace` $ placeholders can be
* used in the second argument, so that e.g. $1 will insert the 1st matched group.
*/
matchReplaceBody?: Array<[string | RegExp, string]>;
}
/**
* @internal
*/
export interface SerializedPassThroughData {
type: 'passthrough';
forwardToLocation?: string;
forwarding?: ForwardingOptions;
proxyConfig?: SerializedProxyConfig;
ignoreHostCertificateErrors?: string[] | boolean;
extraCACertificates?: Array<{
cert: string;
} | {
certPath: string;
}>;
clientCertificateHostMap?: {
[host: string]: {
pfx: string;
passphrase?: string;
};
};
lookupOptions?: PassThroughLookupOptions;
simulateConnectionErrors?: boolean;
transformRequest?: Replace<RequestTransform, {
'replaceBody'?: string;
'updateHeaders'?: string;
'updateJsonBody'?: string;
'matchReplaceBody'?: Array<[
string | {
regexSource: string;
flags: string;
},
string
]>;
}>;
transformResponse?: Replace<ResponseTransform, {
'replaceBody'?: string;
'updateHeaders'?: string;
'updateJsonBody'?: string;
'matchReplaceBody'?: Array<[
string | {
regexSource: string;
flags: string;
},
string
]>;
}>;
hasBeforeRequestCallback?: boolean;
hasBeforeResponseCallback?: boolean;
}
/**
* @internal
*/
export interface BeforePassthroughRequestRequest {
args: [Replace<CompletedRequest, {
body: string;
}>];
}
/**
* @internal
*/
export interface BeforePassthroughResponseRequest {
args: [Replace<PassThroughResponse, {
body: string;
}>];
}
/**
* Used in merging as a marker for values to omit, because lodash ignores undefineds.
* @internal
*/
export declare const SERIALIZED_OMIT = "__mockttp__transform__omit__";
export declare class PassThroughHandlerDefinition extends Serializable implements RequestHandlerDefinition {
readonly type = "passthrough";
readonly forwarding?: ForwardingOptions;
readonly ignoreHostHttpsErrors: string[] | boolean;
readonly clientCertificateHostMap: {
[host: string]: {
pfx: Buffer;
passphrase?: string;
};
};
readonly extraCACertificates: Array<{
cert: string | Buffer;
} | {
certPath: string;
}>;
readonly transformRequest?: RequestTransform;
readonly transformResponse?: ResponseTransform;
readonly beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult | void> | void;
readonly beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult | void> | void;
readonly proxyConfig?: ProxyConfig;
readonly lookupOptions?: PassThroughLookupOptions;
readonly simulateConnectionErrors: boolean;
protected outgoingSockets: Set<net.Socket>;
constructor(options?: PassThroughHandlerOptions);
explain(): string;
/**
* @internal
*/
serialize(channel: ClientServerChannel): SerializedPassThroughData;
}
export declare class CloseConnectionHandlerDefinition extends Serializable implements RequestHandlerDefinition {
readonly type = "close-connection";
explain(): string;
}
export declare class ResetConnectionHandlerDefinition extends Serializable implements RequestHandlerDefinition {
readonly type = "reset-connection";
explain(): string;
}
export declare class TimeoutHandlerDefinition extends Serializable implements RequestHandlerDefinition {
readonly type = "timeout";
explain(): string;
}
export declare class JsonRpcResponseHandlerDefinition extends Serializable implements RequestHandlerDefinition {
readonly result: {
result: any;
error?: undefined;
} | {
error: any;
result?: undefined;
};
readonly type = "json-rpc-response";
constructor(result: {
result: any;
error?: undefined;
} | {
error: any;
result?: undefined;
});
explain(): string;
}
export declare const HandlerDefinitionLookup: {
simple: typeof SimpleHandlerDefinition;
callback: typeof CallbackHandlerDefinition;
stream: typeof StreamHandlerDefinition;
file: typeof FileHandlerDefinition;
passthrough: typeof PassThroughHandlerDefinition;
'close-connection': typeof CloseConnectionHandlerDefinition;
'reset-connection': typeof ResetConnectionHandlerDefinition;
timeout: typeof TimeoutHandlerDefinition;
'json-rpc-response': typeof JsonRpcResponseHandlerDefinition;
};