mockttp
Version:
Mock HTTP server for testing HTTP clients and stubbing webservices
1,079 lines (990 loc) • 45 kB
text/typescript
import { stripIndent } from "common-tags";
import * as cors from 'cors';
import type { CAOptions } from './util/certificates';
import { RequestRuleBuilder } from "./rules/requests/request-rule-builder";
import { WebSocketRuleBuilder } from "./rules/websockets/websocket-rule-builder";
import {
ProxyEnvConfig,
MockedEndpoint,
Method,
CompletedRequest,
CompletedResponse,
TlsPassthroughEvent,
TlsHandshakeFailure,
InitiatedRequest,
ClientError,
RulePriority,
WebSocketMessage,
WebSocketClose,
AbortedRequest,
RuleEvent,
RawPassthroughEvent,
RawPassthroughDataEvent,
InitiatedResponse,
BodyData
} from "./types";
import type { RequestRuleData } from "./rules/requests/request-rule";
import type { WebSocketRuleData } from "./rules/websockets/websocket-rule";
import type { SocksServerOptions } from "./server/socks-server";
export type PortRange = { startPort: number, endPort: number };
/**
* A mockttp instance allow you to start and stop mock servers and control their behaviour.
*
* This should be created using the exported {@link getLocal} or {@link getRemote} methods, like
* so:
*
* ```
* const mockServer = require('mockttp').getLocal()
* ```
*
* Call `.start()` to set up a server on a random port, use `.forX` methods like `.forGet(url)`,
* `.forPost(url)` and `.forAnyRequest()` to get a {@link RequestRuleBuilder} and start defining
* mock rules. You can also mock WebSocket requests using `.forAnyWebSocket()`. Call `.stop()`
* when your test is complete. An example:
*
* ```
* await mockServer.start();
* await mockServer.forGet('/abc').thenReply(200, "a response");
* // ...Make some requests
* await mockServer.stop();
* ```
*/
export interface Mockttp {
/**
* Start a mock server.
*
* Specify a fixed port if you need one.
*
* If you don't, a random port will be chosen, which you can get later with `.port`,
* or by using `.url` and `.urlFor(path)` to generate your URLs automatically.
*
* If you need to allow port selection, but in a specific range, pass a
* { startPort, endPort } pair to define the allowed (inclusive) range.
*
* @category Setup
*/
start(port?: number | PortRange): Promise<void>;
/**
* Stop the mock server and reset all rules and subscriptions.
*
* @category Setup
*/
stop(): Promise<void>;
/**
* Enable extra debug output so you can understand exactly what the server is doing.
*
* @category Setup
*/
enableDebug(): void;
/**
* Reset the stored rules and subscriptions. Most of the time it's better to start & stop
* the server instead, but this can be useful in some special cases.
*
* @category Setup
*/
reset(): void;
/**
* The root URL of the server.
*
* This will throw an error if read before the server is started.
*
* @category Metadata
*/
url: string;
/**
* The URL for a given path on the server.
*
* This will throw an error if read before the server is started.
*
* @category Metadata
*/
urlFor(path: string): string;
/**
* The port the server is running on.
*
* This will throw an error if read before the server is started.
*
* @category Metadata
*/
port: number;
/**
* The environment variables typically needed to use this server as a proxy, in a format you
* can add to your environment straight away.
*
* This will throw an error if read before the server is started.
*
* ```
* process.env = Object.assign(process.env, mockServer.proxyEnv)
* ```
*
* @category Metadata
*/
proxyEnv: ProxyEnvConfig;
/**
* Get a builder for a mock rule that will match any requests on any path.
*
* This only matches traditional HTTP requests, not websockets, which are handled
* separately. To match websockets, use `.forAnyWebSocket()`.
*
* @category Mock HTTP requests
*/
forAnyRequest(): RequestRuleBuilder;
/**
* Get a builder for a fallback mock rule that will match any unmatched requests
* on any path.
*
* Fallback rules act like any other rule, but they only match if there is no
* existing normal rule that matches the request, or if all existing rules have
* an explicit execution limit (like `once()`) that has been completed.
*
* @category Mock HTTP requests
*/
forUnmatchedRequest(): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match GET requests for the given path.
* If no path is specified, this matches all GET requests.
*
* The path can be either a string, or a regular expression to match against.
* Path matching always ignores query parameters. To match query parameters,
* use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* There are a few supported matching formats:
* - Relative string paths (`/abc`) will be compared only to the request's path,
* independent of the host & protocol, ignoring query params.
* - Absolute string paths with no protocol (`localhost:8000/abc`) will be
* compared to the URL independent of the protocol, ignoring query params.
* - Fully absolute string paths (`http://localhost:8000/abc`) will be compared
* to entire URL, ignoring query params.
* - Regular expressions can match the absolute URL: `/^http:\/\/localhost:8000\/abc$/`
* - Regular expressions can also match the path: `/^\/abc/`
*
* @category Mock HTTP requests
*/
forGet(url?: string | RegExp): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match POST requests for the given path.
* If no path is specified, this matches all POST requests.
*
* The path can be either a string, or a regular expression to match against.
* Path matching always ignores query parameters. To match query parameters,
* use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* There are a few supported matching formats:
* - Relative string paths (`/abc`) will be compared only to the request's path,
* independent of the host & protocol, ignoring query params.
* - Absolute string paths with no protocol (`localhost:8000/abc`) will be
* compared to the URL independent of the protocol, ignoring query params.
* - Fully absolute string paths (`http://localhost:8000/abc`) will be compared
* to entire URL, ignoring query params.
* - Regular expressions can match the absolute URL: `/^http:\/\/localhost:8000\/abc$/`
* - Regular expressions can also match the path: `/^\/abc/`
*
* @category Mock HTTP requests
*/
forPost(url?: string | RegExp): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match PUT requests for the given path.
* If no path is specified, this matches all PUT requests.
*
* The path can be either a string, or a regular expression to match against.
* Path matching always ignores query parameters. To match query parameters,
* use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* There are a few supported matching formats:
* - Relative string paths (`/abc`) will be compared only to the request's path,
* independent of the host & protocol, ignoring query params.
* - Absolute string paths with no protocol (`localhost:8000/abc`) will be
* compared to the URL independent of the protocol, ignoring query params.
* - Fully absolute string paths (`http://localhost:8000/abc`) will be compared
* to entire URL, ignoring query params.
* - Regular expressions can match the absolute URL: `/^http:\/\/localhost:8000\/abc$/`
* - Regular expressions can also match the path: `/^\/abc/`
*
* @category Mock HTTP requests
*/
forPut(url?: string | RegExp): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match DELETE requests for the given path.
* If no path is specified, this matches all DELETE requests.
*
* The path can be either a string, or a regular expression to match against.
* Path matching always ignores query parameters. To match query parameters,
* use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* There are a few supported matching formats:
* - Relative string paths (`/abc`) will be compared only to the request's path,
* independent of the host & protocol, ignoring query params.
* - Absolute string paths with no protocol (`localhost:8000/abc`) will be
* compared to the URL independent of the protocol, ignoring query params.
* - Fully absolute string paths (`http://localhost:8000/abc`) will be compared
* to entire URL, ignoring query params.
* - Regular expressions can match the absolute URL: `/^http:\/\/localhost:8000\/abc$/`
* - Regular expressions can also match the path: `/^\/abc/`
*
* @category Mock HTTP requests
*/
forDelete(url?: string | RegExp): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match PATCH requests for the given path.
* If no path is specified, this matches all PATCH requests.
*
* The path can be either a string, or a regular expression to match against.
* Path matching always ignores query parameters. To match query parameters,
* use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* There are a few supported matching formats:
* - Relative string paths (`/abc`) will be compared only to the request's path,
* independent of the host & protocol, ignoring query params.
* - Absolute string paths with no protocol (`localhost:8000/abc`) will be
* compared to the URL independent of the protocol, ignoring query params.
* - Fully absolute string paths (`http://localhost:8000/abc`) will be compared
* to entire URL, ignoring query params.
* - Regular expressions can match the absolute URL: `/^http:\/\/localhost:8000\/abc$/`
* - Regular expressions can also match the path: `/^\/abc/`
*
* @category Mock HTTP requests
*/
forPatch(url?: string | RegExp): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match HEAD requests for the given path.
* If no path is specified, this matches all HEAD requests.
*
* The path can be either a string, or a regular expression to match against.
* Path matching always ignores query parameters. To match query parameters,
* use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* There are a few supported matching formats:
* - Relative string paths (`/abc`) will be compared only to the request's path,
* independent of the host & protocol, ignoring query params.
* - Absolute string paths with no protocol (`localhost:8000/abc`) will be
* compared to the URL independent of the protocol, ignoring query params.
* - Fully absolute string paths (`http://localhost:8000/abc`) will be compared
* to entire URL, ignoring query params.
* - Regular expressions can match the absolute URL: `/^http:\/\/localhost:8000\/abc$/`
* - Regular expressions can also match the path: `/^\/abc/`
*
* @category Mock HTTP requests
*/
forHead(url?: string | RegExp): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match OPTIONS requests for the given path.
*
* The path can be either a string, or a regular expression to match against.
* Path matching always ignores query parameters. To match query parameters,
* use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* There are a few supported matching formats:
* - Relative string paths (`/abc`) will be compared only to the request's path,
* independent of the host & protocol, ignoring query params.
* - Absolute string paths with no protocol (`localhost:8000/abc`) will be
* compared to the URL independent of the protocol, ignoring query params.
* - Fully absolute string paths (`http://localhost:8000/abc`) will be compared
* to entire URL, ignoring query params.
* - Regular expressions can match the absolute URL: `/^http:\/\/localhost:8000\/abc$/`
* - Regular expressions can also match the path: `/^\/abc/`
*
* This can only be used if the `cors` option has been set to false.
*
* If cors is true (the default when using a remote client, e.g. in the browser),
* then the mock server automatically handles OPTIONS requests to ensure requests
* to the server are allowed by clients observing CORS rules.
*
* You can pass `{cors: false}` to `getLocal`/`getRemote` to disable this behaviour,
* but if you're testing in a browser you will need to ensure you mock all OPTIONS
* requests appropriately so that the browser allows your other requests to be sent.
*
* @category Mock HTTP requests
*/
forOptions(url?: string | RegExp): RequestRuleBuilder;
/**
* Match JSON-RPC requests, optionally matching a given method and/or params.
*
* If no method or params are specified, this will match all JSON-RPC requests.
*
* Params are matched flexibly, using the same logic as .withJsonBodyIncluding(),
* so only the included fields are checked and other extra fields are ignored
*
* @category Mock HTTP requests
*/
forJsonRpcRequest(match?: { method?: string, params?: any }): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match websocket connections.
*
* This can optionally include a path to match: either a string, or a regular
* expression. Path matching always ignores query parameters. To match query
* parameters, use .withQuery({ a: 'b' }) or withExactQuery('?a=b').
*
* @category Mock websockets
*/
forAnyWebSocket(url?: string | RegExp): WebSocketRuleBuilder;
/**
* Subscribe to hear about request details as soon as the initial request details
* (method, path & headers) are received, without waiting for the body.
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'request-initiated', callback: (req: InitiatedRequest) => void): Promise<void>;
/**
* Subscribe to hear about request body data live, streaming in progressive
* chunks as it's received (listen for `request` if you just want to receive
* the complete body once it's done).
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'request-body-data', callback: (req: BodyData) => void): Promise<void>;
/**
* Subscribe to hear about request details once the request is fully received.
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'request', callback: (req: CompletedRequest) => void): Promise<void>;
/**
* Subscribe to hear about response details as soon as the initial response (the
* status code & headers) are sent, without waiting for the body.
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'response-initiated', callback: (req: InitiatedResponse) => void): Promise<void>;
/**
* Subscribe to hear about response body data live, streaming in progressive
* chunks as it's received (listen for `response` if you just want to receive
* the complete body once it's done).
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'response-body-data', callback: (req: BodyData) => void): Promise<void>;
/**
* Subscribe to hear about response details when the response is completed.
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'response', callback: (req: CompletedResponse) => void): Promise<void>;
/**
* Subscribe to hear about websocket connection requests. This event fires when the
* initial WebSocket request is completed, regardless of whether the request is
* accepted.
*
* This is only useful in some niche use cases, such as logging all websockets seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'websocket-request', callback: (req: CompletedRequest) => void): Promise<void>;
/**
* Subscribe to hear about websocket connection upgrades. This event fires when a
* WebSocket request is accepted, returning the HTTP response body that was sent
* before the WebSocket stream starts.
*
* This is only useful in some niche use cases, such as logging all websockets seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'websocket-accepted', callback: (req: CompletedResponse) => void): Promise<void>;
/**
* Subscribe to hear about websocket messages received by Mockttp from its downstream
* websocket clients. This event fires whenever any data is received on an open
* mocked WebSocket.
*
* This is only useful in some niche use cases, such as logging all websockets seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'websocket-message-received', callback: (req: WebSocketMessage) => void): Promise<void>;
/**
* Subscribe to hear about websocket messages sent by Mockttp to its downstream
* websocket clients. This event fires whenever any data is sent on an open
* mocked WebSocket.
*
* This is only useful in some niche use cases, such as logging all websockets seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'websocket-message-sent', callback: (req: WebSocketMessage) => void): Promise<void>;
/**
* Subscribe to hear when a websocket connection is closed. This fires only for clean
* websocket shutdowns, after the websocket was initially accepted. If the connection
* is closed uncleanly, an 'abort' event will fire instead. If the websocket was
* initially rejected explicitly, a 'response' event (with the rejecting response) will
* fire instead.
*
* This is only useful in some niche use cases, such as logging all websockets seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'websocket-close', callback: (req: WebSocketClose) => void): Promise<void>;
/**
* Subscribe to hear about requests that are aborted before the request or
* response is fully completed.
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'abort', callback: (req: AbortedRequest) => void): Promise<void>;
/**
* Subscribe to hear about TLS connections that are passed through the proxy without
* interception, due to the `tlsPassthrough` HTTPS option.
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server, independently of the rules defined.
*
* The callback will be called asynchronously from connection handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'tls-passthrough-opened', callback: (req: TlsPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to hear about closure of TLS connections that were passed through the
* proxy without interception, due to the `tlsPassthrough` HTTPS option.
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server, independently of the rules defined.
*
* The callback will be called asynchronously from connection handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'tls-passthrough-closed', callback: (req: TlsPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to hear about requests that start a TLS handshake, but fail to complete it.
* Not all clients report TLS errors explicitly, so this event fires for explicitly
* reported TLS errors, and for TLS connections that are immediately closed with no
* data sent.
*
* This is typically useful to detect clients who aren't correctly configured to trust
* the configured HTTPS certificate. The callback is given the host name provided
* by the client via SNI, if SNI was used (it almost always is).
*
* This is only useful in some niche use cases, such as logging all requests seen
* by the server, independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'tls-client-error', callback: (req: TlsHandshakeFailure) => void): Promise<void>;
/**
* Subscribe to hear about requests that fail before successfully sending their
* initial parameters (the request line & headers). This will fire for requests
* that drop connections early, send invalid or too-long headers, or aren't
* correctly parseable in some form.
*
* This is typically useful to detect clients who aren't correctly configured.
* The callback is given an object containing the request (as we were best
* able to parse it) and either the error response returned, or 'aborted'
* if the connection was disconnected before the server could respond.
*
* This is only useful in some niche use cases, such as logging all requests
* seen by the server, independently of the rules defined.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'client-error', callback: (error: ClientError) => void): Promise<void>;
/**
* Subscribe to hear about connections that are passed through the proxy without
* interception, due to the `passthrough` option.
*
* This is separate to TLS passthrough: raw passthrough happens automatically
* before any TLS handshake is received (so includes no TLS data, and may use any
* protocol) generally because the protocol on the connection is not HTTP. TLS
* passthrough happens after the TLS client hello has been received, only if it
* has matched a rule defined in the tlsPassthrough options (e.g. a specific
* hostname).
*
* @category Events
*/
on(event: 'raw-passthrough-opened', callback: (req: RawPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to hear about close of connections that are passed through the proxy
* without interception, due to the `passthrough` option. See `raw-passthrough-opened`
* for more details.
*
* @category Events
*/
on(event: 'raw-passthrough-closed', callback: (req: RawPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to hear about each chunk of data that is passed through the raw passthrough
* non-intercepted tunnels, due to the `passthrough` option. See `raw-passthrough-opened`
* for more details.
*
* @category Events
*/
on(event: 'raw-passthrough-data', callback: (req: RawPassthroughDataEvent) => void): Promise<void>;
/**
* Some rules may emit events with metadata about request processing. For example,
* passthrough rules may emit events about upstream server interactions.
*
* You can listen to rule-event to hear about all these events. When emitted,
* this will include the id of the request being processed, the id of the rule
* that fired the event, the type of the event, and the event data itself.
*
* This is only useful in some niche use cases, such as logging all proxied upstream
* requests made by the server, separately from the client connections handled.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on<T = unknown>(event: 'rule-event', callback: (event: RuleEvent<T>) => void): Promise<void>;
/**
* Adds the given HTTP request rules to the server.
*
* This API is only useful if you're manually building rules, rather than
* using RequestRuleBuilder, and is only for special cases. This approach may
* be necessary if you need to configure all your rules in one place to
* enable them elsewhere/later.
*
* @category Manual rule definition
*/
addRequestRules(...ruleData: RequestRuleData[]): Promise<MockedEndpoint[]>;
/**
* Adds the given HTTP request rule to the server.
*
* This is a convenient alias for calling `addRequestRules` with one rule,
* and extracting the first endpoint result.
*
* This API is only useful if you're manually building rules, rather than
* using RequestRuleBuilder, and is only for special cases. This approach may
* be necessary if you need to configure all your rules in one place to
* enable them elsewhere/later.
*
* @category Manual rule definition
*/
addRequestRule(ruleData: RequestRuleData): Promise<MockedEndpoint>;
/**
* Set the given HTTP request rules as the only request rules on the server,
* replacing any existing rules (except websocket rules).
*
* This API is only useful if you're manually building rules, rather than
* using RequestRuleBuilder, and is only for special cases. This approach may
* be necessary if you need to configure all your rules in one place to
* enable them elsewhere/later.
*
* @category Manual rule definition
*/
setRequestRules(...ruleData: RequestRuleData[]): Promise<MockedEndpoint[]>;
/**
* Adds the given websocket rules to the server.
*
* This API is only useful if you're manually building rules, rather than
* using RequestRuleBuilder, and is only for special cases. This approach may
* be necessary if you need to configure all your rules in one place to
* enable them elsewhere/later.
*
* @category Manual rule definition
*/
addWebSocketRules(...ruleData: WebSocketRuleData[]): Promise<MockedEndpoint[]>;
/**
* Adds the given websocket rule to the server.
*
* This is a convenient alias for calling `addWebSocketRules` with one rule,
* and extracting the first endpoint result.
*
* This API is only useful if you're manually building rules, rather than
* using RequestRuleBuilder, and is only for special cases. This approach may
* be necessary if you need to configure all your rules in one place to
* enable them elsewhere/later.
*
* @category Manual rule definition
*/
addWebSocketRule(ruleData: WebSocketRuleData): Promise<MockedEndpoint>;
/**
* Set the given websocket rules as the only websocket rules on the server,
* replacing all existing websocket rules (but leaving normal rules untouched).
*
* This API is only useful if you're manually building rules, rather than
* using RequestRuleBuilder, and is only for special cases. This approach may
* be necessary if you need to configure all your rules in one place to
* enable them elsewhere/later.
*
* @category Manual rule definition
*/
setWebSocketRules(...ruleData: WebSocketRuleData[]): Promise<MockedEndpoint[]>;
/**
* Returns the set of currently registered mock endpoints.
*
* @category Metadata
*/
getMockedEndpoints(): Promise<MockedEndpoint[]>;
/**
* Returns the set of registered but pending mock endpoints: endpoints which either
* haven't seen the specified number of requests (if one was specified
* e.g. with .twice()) or which haven't seen at least one request, by default.
*
* @category Metadata
*/
getPendingEndpoints(): Promise<MockedEndpoint[]>;
/**
* List the names of the rule parameters available for rule definitions. These
* parameters are defined by the admin server. This list can be used in some
* advanced use cases to confirm beforehand that the parameters a client wishes to
* reference are available.
*
* Only relevant to remote/browser Mockttp usage. Servers created directly without any
* admin server will never have rule parameters defined, and so this method will always
* return an empty list.
*
* @category Metadata
*/
getRuleParameterKeys(): Promise<string[]>;
}
export type MockttpHttpsOptions = CAOptions & {
/**
* The domain name that will be used in the certificate for incoming TLS
* connections which don't use SNI to request a specific domain.
*/
defaultDomain?: string;
/**
* A list of hostnames where TLS interception should always be skipped.
*
* When a TLS connection is started that references a matching hostname in its
* server name indication (SNI) extension, or which uses a matching hostname
* in a preceeding CONNECT request to create a tunnel, the connection will be
* sent raw to the upstream hostname, without handling TLS within Mockttp (i.e.
* with no TLS interception performed).
*
* This option is mutually exclusive with `tlsInterceptOnly` and setting both
* options will throw an error.
*
* Each element in this list must be an object with a 'hostname' field for the
* hostname that should be matched. Wildcards are supported (following the
* [URLPattern specification](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API)),
* eg. `{hostname: '*.example.com'}`.
*
* In future more options may be supported
* here for additional configuration of this behaviour.
*/
tlsPassthrough?: Array<{ hostname: string }>;
/**
* A limited list of the only hostnames whose TLS should be intercepted.
*
* This is the opposite of `tlsPassthrough`. When set, only connections
* to these hostnames will be intercepted, and all other TLS connections will
* be passed through without interception.
*
* This option is mutually exclusive with `tlsPassthrough` and setting both
* options will throw an error.
*
* Each element in this list must be an object with a 'hostname' field for the
* hostname that should be matched. Wildcards are supported (following the
* [URLPattern specification](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API)),
* eg. `{hostname: '*.example.com'}`.
*
* In future more options may be supported
* here for additional configuration of this behaviour.
*/
tlsInterceptOnly?: Array<{ hostname: string }>;
/**
* Set the TLS server options, used for incoming TLS connections.
*
* The only officially supported option for now is the minimum TLS version, which can
* be used to relax/tighten TLS requirements on clients. If not set, this defaults
* to your Node version's default TLS configuration. The full list of versions can be
* found at https://nodejs.org/api/tls.html#tlssocketgetprotocol.
*/
tlsServerOptions?: {
minVersion?: 'TLSv1.3' | 'TLSv1.2' | 'TLSv1.1' | 'TLSv1';
};
/**
* The path to a file where TLS session keys should be logged. This allows you to combine
* Mockttp with Wireshark and similar, allowing to you inspect the decrypted raw bytes
* and full TLS handshake details, while also using Mockttp for high-level capture
* & traffic modification.
*
* When set, the keys for both client & server sessions will be logged, allowing you
* to examine both sides of the proxied connection.
*/
keyLogFile?: string;
};
export interface MockttpOptions {
/**
* Should the server automatically respond to OPTIONS requests with a permissive
* response?
*
* Defaults to true for remote clients (e.g. in the browser), and false otherwise.
* If this is set to false, browser requests will typically fail unless you
* stub OPTIONS responses by hand.
*/
cors?: boolean | cors.CorsOptions;
/**
* Should the server print extra debug information?
*/
debug?: boolean;
/**
* The HTTPS settings to be used. Optional, only HTTP interception will be
* enabled if omitted. This should be set to either a { key, cert } object
* containing the private key and certificate in PEM format, or a { keyPath,
* certPath } object containing the path to files containing that content.
*/
https?: MockttpHttpsOptions;
/**
* Should HTTP/2 be enabled? Can be true, false, or 'fallback'. If true,
* HTTP/2 is used for all clients supporting it. If false, HTTP/2 is never
* used. If 'fallback' HTTP/2 is used only for clients that do not advertise
* support for HTTP/1.1, but HTTP/1.1 is used by preference in all other
* cases.
*
* Client HTTP/2 support is only advertised as part of the TLS options.
* When no HTTPS configuration is provided, 'fallback' is equivalent to
* false.
*/
http2?: true | 'fallback' | false;
/**
* Should the server accept incoming SOCKS connections? Defaults to false.
*
* If set to true or if detailed options are provided, the server will listen
* for incoming SOCKS connections on the same port as the HTTP server, unwrap
* received connections, and handle them like any other incoming TCP connection
* (intercepting HTTP(S) from within the SOCKS connection as normal).
*
* The only supported option for now is `authMethods`.
*/
socks?: boolean | SocksServerOptions;
/**
* An array of rules for traffic that should be passed through the proxy
* immediately, without interception or modification.
*
* This is subtly different to TLS passthrough/interceptOnly, which only
* apply to TLS connections, and only after the TLS client hello has been
* received and found to match a rule.
*
* For now, the only rule here is 'unknown-protocol', which enables
* passthrough of all unknown protocols (i.e. traffic that is definitely
* not HTTP, HTTP/2, WebSocket, or SOCKS traffic) which are received on
* a proxy connection (a connection carrying end-destination information,
* such as SOCKS - direct connections of unknown data without any final
* destination information from a preceeding tunnel cannot be passed
* through).
*
* Unknown protocol connections that cannot be passed through (because
* this rule is not enabled, or because they are not proxied with a
* destination specified) will be closed with a 400 Bad Request HTTP
* response like any other client HTTP error.
*/
passthrough?: Array<'unknown-protocol'>;
/**
* By default, requests that match no rules will receive an explanation of the
* request & existing rules, followed by some suggested example Mockttp code
* which could be used to match the rule.
*
* In some cases where the end client is unaware of Mockttp, these example
* suggestions are just confusing. Set `suggestChanges` to false to disable it.
*/
suggestChanges?: boolean;
/**
* Record the requests & response for all traffic matched by each rule, and make
* it available via endpoint.getSeenRequests().
*
* Defaults to true. It can be useful to set this to false if lots of data will
* be sent to/via the server, to avoid storing all traffic in memory unnecessarily,
* if getSeenRequests will not be used.
*
* If this is set to false then getSeenRequests() will always return
* an empty array. This only disables the built-in persistence of request data,
* so traffic can still be captured live or stored elsewhere using
* .on('request') & .on('response').
*/
recordTraffic?: boolean;
/**
* The maximum body size to process, in bytes.
*
* Bodies larger than this will be dropped, becoming empty, so they won't match
* body matchers, won't be available in .seenRequests, and won't be included in
* subscribed event data. Body data will still typically be included in passed
* through request & response data, in most cases, so this won't affect the
* external HTTP clients otherwise.
*/
maxBodySize?: number;
}
export type SubscribableEvent =
| 'request-initiated'
| 'request-body-data'
| 'request'
| 'response-initiated'
| 'response-body-data'
| 'response'
| 'websocket-request'
| 'websocket-accepted'
| 'websocket-message-received'
| 'websocket-message-sent'
| 'websocket-close'
| 'abort'
| 'tls-passthrough-opened'
| 'tls-passthrough-closed'
| 'tls-client-error'
| 'client-error'
| 'raw-passthrough-opened'
| 'raw-passthrough-closed'
| 'raw-passthrough-data'
| 'rule-event';
/**
* @hidden
*/
export abstract class AbstractMockttp {
protected corsOptions: boolean | cors.CorsOptions;
protected debug: boolean;
protected recordTraffic: boolean;
protected suggestChanges: boolean;
abstract get url(): string;
abstract on(
event: SubscribableEvent,
callback: (req: CompletedRequest) => void
): Promise<void>;
constructor(options: MockttpOptions) {
this.debug = options.debug || false;
this.corsOptions = options.cors || false;
this.recordTraffic = options.recordTraffic !== undefined
? options.recordTraffic
: true;
this.suggestChanges = options.suggestChanges !== undefined
? options.suggestChanges
: true;
}
get proxyEnv(): ProxyEnvConfig {
return {
HTTP_PROXY: this.url,
HTTPS_PROXY: this.url
}
}
urlFor(path: string): string {
return this.url + path;
}
abstract addRequestRules: (...ruleData: RequestRuleData[]) => Promise<MockedEndpoint[]>;
addRequestRule = (rule: RequestRuleData) =>
this.addRequestRules(rule).then((rules) => rules[0]);
abstract setRequestRules(...ruleData: RequestRuleData[]): Promise<MockedEndpoint[]>;
abstract addWebSocketRules: (...ruleData: WebSocketRuleData[]) => Promise<MockedEndpoint[]>;
addWebSocketRule = (rule: WebSocketRuleData) =>
this.addWebSocketRules(rule).then((rules) => rules[0]);
abstract setWebSocketRules(...ruleData: WebSocketRuleData[]): Promise<MockedEndpoint[]>;
forAnyRequest(): RequestRuleBuilder {
return new RequestRuleBuilder(this.addRequestRule);
}
forUnmatchedRequest(): RequestRuleBuilder {
return new RequestRuleBuilder(this.addRequestRule)
.asPriority(RulePriority.FALLBACK);
}
forGet(url?: string | RegExp): RequestRuleBuilder {
return new RequestRuleBuilder(Method.GET, url, this.addRequestRule);
}
forPost(url?: string | RegExp): RequestRuleBuilder {
return new RequestRuleBuilder(Method.POST, url, this.addRequestRule);
}
forPut(url?: string | RegExp): RequestRuleBuilder {
return new RequestRuleBuilder(Method.PUT, url, this.addRequestRule);
}
forDelete(url?: string | RegExp): RequestRuleBuilder {
return new RequestRuleBuilder(Method.DELETE, url, this.addRequestRule);
}
forPatch(url?: string | RegExp): RequestRuleBuilder {
return new RequestRuleBuilder(Method.PATCH, url, this.addRequestRule);
}
forHead(url?: string | RegExp): RequestRuleBuilder {
return new RequestRuleBuilder(Method.HEAD, url, this.addRequestRule);
}
forOptions(url?: string | RegExp): RequestRuleBuilder {
if (this.corsOptions) {
throw new Error(stripIndent`
Cannot mock OPTIONS requests with CORS enabled.
You can disable CORS by passing { cors: false } to getLocal/getRemote, but this may cause issues ${''
}connecting to your mock server from browsers, unless you mock all required OPTIONS preflight ${''
}responses by hand.
`);
}
return new RequestRuleBuilder(Method.OPTIONS, url, this.addRequestRule);
}
forJsonRpcRequest(match: { method?: string, params?: any } = {}) {
return new RequestRuleBuilder(this.addRequestRule)
.withJsonBodyIncluding({
jsonrpc: '2.0',
...(match.method ? { method: match.method } : {}),
...(match.params ? { params: match.params } : {})
});
}
forAnyWebSocket(url?: string | RegExp): WebSocketRuleBuilder {
return new WebSocketRuleBuilder(url, this.addWebSocketRule);
}
}