Socket
Socket
Sign inDemoInstall

mockttp

Package Overview
Dependencies
Maintainers
1
Versions
125
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mockttp - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0

dist/util/dns.d.ts

6

custom-typings/node-type-extensions.d.ts

@@ -95,2 +95,8 @@ // There's a few places where we attach extra data to some node objects during

class ServerHttp2Stream {
// Treated the same as net.Socket, when we unwrap them in our combo server:
lastHopEncrypted?: net.Socket['lastHopEncrypted'];
__timingInfo?: net.Socket['__timingInfo'];
}
// A constant symbol used in-band in HTTP/2 header objects to list the headers

@@ -97,0 +103,0 @@ // that shouldn't/weren't automatically compressed. Only defined in Node 15+.

46

dist/client/mockttp-client.d.ts

@@ -31,2 +31,7 @@ /**

/**
* The full URL to use for a standalone server with remote (or local but browser) client.
* When using a local server, this parameter is ignored.
*/
standaloneServerUrl?: string;
/**
* Options to include on all client requests, e.g. to add extra

@@ -42,2 +47,32 @@ * headers for authentication.

/**
* Reset a remote standalone server, shutting down all Mockttp servers controlled by that
* standalone server. This is equivalent to calling `client.stop()` for all remote
* clients of the target server.
*
* This can be useful in some rare cases, where a client might fail to reliably tear down
* its own server, e.g. in Cypress testing. In this case, it's useful to reset the
* standalone server completely remotely without needing access to any previous client
* instances, to ensure all servers from previous test runs have been shut down.
*
* After this is called, behaviour of any previously connected clients is undefined, and
* it's likely that they may throw errors or experience other undefined behaviour. Ensure
* that `client.stop()` has been called on all active clients before calling this method.
*/
export declare function resetStandalone(options?: {
/**
* The full URL to use for a standalone server with remote (or local but browser) client.
* When using a local server, this parameter is ignored.
*/
standaloneServerUrl?: string;
/**
* Options to include on all client requests, e.g. to add extra
* headers for authentication.
*/
client?: {
headers?: {
[key: string]: string;
};
};
}): Promise<void>;
/**
* A Mockttp implementation, controlling a remote Mockttp standalone server.

@@ -48,11 +83,10 @@ *

*/
export default class MockttpClient extends AbstractMockttp implements Mockttp {
export declare class MockttpClient extends AbstractMockttp implements Mockttp {
private mockServerOptions;
private mockClientOptions;
private mockServerConfig;
private mockServerSchema;
private mockServerStream;
private mockServerSchema;
private subscriptionClient;
constructor(options?: MockttpClientOptions);
private requestFromStandalone;
private openStreamToMockServer;

@@ -64,2 +98,3 @@ private prepareSubscriptionClientToMockServer;

stop(): Promise<void>;
private completeShutdown;
private typeHasField;

@@ -69,3 +104,3 @@ private optionalField;

enableDebug(): void;
reset: () => Promise<boolean>;
reset: () => Promise<void>;
get url(): string;

@@ -77,3 +112,4 @@ get port(): number;

setWebSocketRules: (...rules: WebSocketRuleData[]) => Promise<MockedEndpoint[]>;
private _addRules;
private _addRequestRules;
setFallbackRequestRule: (rule: RequestRuleData) => Promise<MockedEndpoint>;
private _addWsRules;

@@ -80,0 +116,0 @@ getMockedEndpoints(): Promise<MockedEndpointClient[]>;

145

dist/client/mockttp-client.js

@@ -15,3 +15,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.GraphQLError = exports.RequestError = exports.ConnectionError = void 0;
exports.MockttpClient = exports.resetStandalone = exports.GraphQLError = exports.RequestError = exports.ConnectionError = void 0;
const typed_error_1 = require("typed-error");

@@ -96,3 +96,57 @@ const getFetchPonyfill = require("fetch-ponyfill");

}
function requestFromStandalone(serverUrl, path, options) {
return __awaiter(this, void 0, void 0, function* () {
const url = `${serverUrl}${path}`;
let response;
try {
response = yield fetch(url, options);
}
catch (e) {
if (e.code === 'ECONNREFUSED') {
throw new ConnectionError(`Failed to connect to standalone server at ${serverUrl}`);
}
else
throw e;
}
if (response.status >= 400) {
let body = yield response.text();
let jsonBody = null;
try {
jsonBody = JSON.parse(body);
}
catch (e) { }
if (jsonBody && jsonBody.error) {
throw new RequestError(jsonBody.error, response);
}
else {
throw new RequestError(`Request to ${url} failed, with status ${response.status} and response body: ${body}`, response);
}
}
else {
return response.json();
}
});
}
/**
* Reset a remote standalone server, shutting down all Mockttp servers controlled by that
* standalone server. This is equivalent to calling `client.stop()` for all remote
* clients of the target server.
*
* This can be useful in some rare cases, where a client might fail to reliably tear down
* its own server, e.g. in Cypress testing. In this case, it's useful to reset the
* standalone server completely remotely without needing access to any previous client
* instances, to ensure all servers from previous test runs have been shut down.
*
* After this is called, behaviour of any previously connected clients is undefined, and
* it's likely that they may throw errors or experience other undefined behaviour. Ensure
* that `client.stop()` has been called on all active clients before calling this method.
*/
function resetStandalone(options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const serverUrl = options.standaloneServerUrl || `http://localhost:${types_1.DEFAULT_STANDALONE_PORT}`;
yield requestFromStandalone(serverUrl, '/reset', Object.assign(Object.assign({}, options.client), { method: 'POST' }));
});
}
exports.resetStandalone = resetStandalone;
/**
* A Mockttp implementation, controlling a remote Mockttp standalone server.

@@ -113,10 +167,10 @@ *

return (yield this.queryMockServer(`mutation Reset {
reset
reset
}`));
});
this.addRequestRules = (...rules) => __awaiter(this, void 0, void 0, function* () {
return this._addRules(rules, false);
return this._addRequestRules(rules, false);
});
this.setRequestRules = (...rules) => __awaiter(this, void 0, void 0, function* () {
return this._addRules(rules, true);
return this._addRequestRules(rules, true);
});

@@ -129,3 +183,3 @@ this.addWebSocketRules = (...rules) => __awaiter(this, void 0, void 0, function* () {

});
this._addRules = (rules, reset) => __awaiter(this, void 0, void 0, function* () {
this._addRequestRules = (rules, reset) => __awaiter(this, void 0, void 0, function* () {
if (!this.mockServerConfig)

@@ -149,3 +203,3 @@ throw new Error('Cannot add rules before the server is started');

const mutationName = reset ? 'setRules' : 'addRules';
let endpoints = (yield this.queryMockServer(`mutation ${requestName}($newRules: [MockRule!]!) {
let { endpoints } = (yield this.queryMockServer(`mutation ${requestName}($newRules: [MockRule!]!) {
endpoints: ${mutationName}(input: $newRules) {

@@ -163,5 +217,18 @@ id,

})
})).endpoints;
}));
return endpoints.map(({ id, explanation }) => new mocked_endpoint_client_1.MockedEndpointClient(id, explanation, this.getEndpointDataGetter(id)));
});
this.setFallbackRequestRule = (rule) => __awaiter(this, void 0, void 0, function* () {
if (!this.mockServerConfig)
throw new Error('Cannot add rules before the server is started');
let { endpoint: { id, explanation } } = (yield this.queryMockServer(`mutation SetFallbackRule($fallbackRule: MockRule!) {
endpoint: setFallbackRule(input: $fallbackRule) {
id,
explanation
}
}`, {
fallbackRule: rule_serialization_1.serializeRuleData(rule, this.mockServerStream)
}));
return new mocked_endpoint_client_1.MockedEndpointClient(id, explanation, this.getEndpointDataGetter(id));
});
this._addWsRules = (rules, reset) => __awaiter(this, void 0, void 0, function* () {

@@ -231,35 +298,2 @@ // Seperate and much simpler than _addRules, because it doesn't have to deal with

}
requestFromStandalone(path, options) {
return __awaiter(this, void 0, void 0, function* () {
const url = `${this.mockServerOptions.standaloneServerUrl}${path}`;
let response;
try {
response = yield fetch(url, mergeClientOptions(options, this.mockClientOptions));
}
catch (e) {
if (e.code === 'ECONNREFUSED') {
throw new ConnectionError(`Failed to connect to standalone server at ${this.mockServerOptions.standaloneServerUrl}`);
}
else
throw e;
}
if (response.status >= 400) {
let body = yield response.text();
let jsonBody = null;
try {
jsonBody = JSON.parse(body);
}
catch (e) { }
if (jsonBody && jsonBody.error) {
throw new RequestError(jsonBody.error, response);
}
else {
throw new RequestError(`Request to ${url} failed, with status ${response.status} and response body: ${body}`, response);
}
}
else {
return response.json();
}
});
}
openStreamToMockServer(config) {

@@ -272,2 +306,4 @@ var _a;

});
// When this stream closes, the server is gone and need to shut down.
stream.on('close', () => this.completeShutdown());
return new Promise((resolve, reject) => {

@@ -345,3 +381,3 @@ stream.once('connect', () => resolve(stream));

const path = portConfig ? `/start?port=${JSON.stringify(portConfig)}` : '/start';
let mockServerConfig = yield this.requestFromStandalone(path, {
let mockServerConfig = yield requestFromStandalone(this.mockServerOptions.standaloneServerUrl, path, mergeClientOptions({
method: 'POST',

@@ -352,3 +388,3 @@ headers: new Headers({

body: JSON.stringify(this.mockServerOptions)
});
}, this.mockClientOptions));
// Also open a stream connection, for 2-way communication we might need later.

@@ -373,5 +409,26 @@ this.mockServerStream = yield this.openStreamToMockServer(mockServerConfig);

});
this.mockServerConfig = this.mockServerStream = undefined;
this.mockServerStream = undefined;
this.mockServerSchema = undefined;
this.subscriptionClient = undefined;
this.mockServerConfig = undefined;
});
}
// Called when the remote server appears to have closed: shut down directly,
// no need to close things up nicely.
completeShutdown() {
if (!this.mockServerConfig)
return;
try {
this.mockServerStream.end();
}
catch (e) { }
try {
this.subscriptionClient.close();
}
catch (e) { }
this.mockServerStream = undefined;
this.mockServerSchema = undefined;
this.subscriptionClient = undefined;
this.mockServerConfig = undefined;
}
typeHasField(typeName, fieldName) {

@@ -602,3 +659,3 @@ const type = _.find(this.mockServerSchema.types, { name: typeName });

}
exports.default = MockttpClient;
exports.MockttpClient = MockttpClient;
//# sourceMappingURL=mockttp-client.js.map

@@ -0,1 +1,2 @@

import { resetStandalone } from "./client/mockttp-client";
import { Mockttp, MockttpOptions } from "./mockttp";

@@ -12,1 +13,2 @@ export { Method } from "./types";

export declare function getStandalone(options?: any): never;
export { resetStandalone };
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStandalone = exports.getRemote = exports.getLocal = exports.handlers = exports.completionCheckers = exports.webSocketHandlers = exports.requestHandlers = exports.matchers = exports.Method = void 0;
exports.resetStandalone = exports.getStandalone = exports.getRemote = exports.getLocal = exports.handlers = exports.completionCheckers = exports.webSocketHandlers = exports.requestHandlers = exports.matchers = exports.Method = void 0;
const mockttp_client_1 = require("./client/mockttp-client");
Object.defineProperty(exports, "resetStandalone", { enumerable: true, get: function () { return mockttp_client_1.resetStandalone; } });
var types_1 = require("./types");

@@ -19,7 +20,7 @@ Object.defineProperty(exports, "Method", { enumerable: true, get: function () { return types_1.Method; } });

function getLocal(options = {}) {
return new mockttp_client_1.default(options);
return new mockttp_client_1.MockttpClient(options);
}
exports.getLocal = getLocal;
function getRemote(options = {}) {
return new mockttp_client_1.default(options);
return new mockttp_client_1.MockttpClient(options);
}

@@ -26,0 +27,0 @@ exports.getRemote = getRemote;

/**
* @module Mockttp
*/
import { MockttpClientOptions } from "./client/mockttp-client";
import { MockttpClientOptions, resetStandalone } from "./client/mockttp-client";
import { MockttpStandalone, StandaloneServerOptions } from "./standalone/mockttp-standalone";

@@ -52,1 +52,2 @@ import { Mockttp, MockttpOptions } from "./mockttp";

export declare function getStandalone(options?: StandaloneServerOptions): MockttpStandalone;
export { resetStandalone };

@@ -6,5 +6,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.getStandalone = exports.getRemote = exports.getLocal = exports.generateSPKIFingerprint = exports.generateCACertificate = exports.handlers = exports.completionCheckers = exports.webSocketHandlers = exports.requestHandlers = exports.matchers = exports.Method = void 0;
exports.resetStandalone = exports.getStandalone = exports.getRemote = exports.getLocal = exports.generateSPKIFingerprint = exports.generateCACertificate = exports.handlers = exports.completionCheckers = exports.webSocketHandlers = exports.requestHandlers = exports.matchers = exports.Method = void 0;
const mockttp_server_1 = require("./server/mockttp-server");
const mockttp_client_1 = require("./client/mockttp-client");
Object.defineProperty(exports, "resetStandalone", { enumerable: true, get: function () { return mockttp_client_1.resetStandalone; } });
const mockttp_standalone_1 = require("./standalone/mockttp-standalone");

@@ -42,3 +43,3 @@ // Export the core type definitions:

function getLocal(options = {}) {
return new mockttp_server_1.default(options);
return new mockttp_server_1.MockttpServer(options);
}

@@ -53,3 +54,3 @@ exports.getLocal = getLocal;

function getRemote(options = {}) {
return new mockttp_client_1.default(options);
return new mockttp_client_1.MockttpClient(options);
}

@@ -56,0 +57,0 @@ exports.getRemote = getRemote;

@@ -96,2 +96,13 @@ import * as cors from 'cors';

/**
* Get a builder for a fallback mock rule that will match any unmatched requests
* on any path. A fallback rule will only match if there is no existing rule at
* all matching the request, or all existing rules have an explicit limit (like
* `once()`) that has been completed.
*
* Only one unmatched request rule can be registered, and it cannot include any
* matchers. In either of these cases, when the final `thenX()` method is called,
* a rejected promise will be returned.
*/
unmatchedRequest(): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match GET requests for the given path.

@@ -421,7 +432,2 @@ * If no path is specified, this matches all GET requests.

/**
* The full URL to use for a standalone server with remote (or local but browser) client.
* When using a local server, this parameter is ignored.
*/
standaloneServerUrl?: string;
/**
* By default, requests that match no rules will receive an explanation of the

@@ -490,2 +496,3 @@ * request & existing rules, followed by some suggested example Mockttp code

abstract setRequestRules(...ruleData: RequestRuleData[]): Promise<MockedEndpoint[]>;
abstract setFallbackRequestRule(ruleData: RequestRuleData): Promise<MockedEndpoint>;
addRule: (ruleData: RequestRuleData) => Promise<MockedEndpoint>;

@@ -498,2 +505,3 @@ addRules: (...ruleData: RequestRuleData[]) => Promise<MockedEndpoint[]>;

anyRequest(): RequestRuleBuilder;
unmatchedRequest(): RequestRuleBuilder;
get(url?: string | RegExp): RequestRuleBuilder;

@@ -500,0 +508,0 @@ post(url?: string | RegExp): RequestRuleBuilder;

@@ -45,2 +45,5 @@ "use strict";

}
unmatchedRequest() {
return new request_rule_builder_1.RequestRuleBuilder(this.setFallbackRequestRule);
}
get(url) {

@@ -47,0 +50,0 @@ return new request_rule_builder_1.RequestRuleBuilder(types_1.Method.GET, url, this.addRequestRule);

@@ -25,3 +25,4 @@ /**

}
export interface CallbackResponseResult {
export declare type CallbackResponseResult = CallbackResponseMessageResult | 'close';
export interface CallbackResponseMessageResult {
statusCode?: number;

@@ -195,13 +196,10 @@ status?: number;

* and which returns an object that defines how the the request content should
* be changed before it's passed to the upstream server.
* be transformed before it's passed to the upstream server.
*
* The callback should return an object that definies how the request
* should be changed. All fields on the object are optional. The possible
* fields are:
* 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). The possible fields are:
*
* - `method` (a replacement HTTP verb, capitalized)
* - `url` (a full URL to send the request to)
* - `response` (a response callback result: if provided this will be used
* directly, the request will not be passed through at all, and any
* beforeResponse callback will never fire)
* - `headers` (object with string keys & values, replaces all headers if set)

@@ -211,13 +209,19 @@ * - `body` (string or buffer, replaces the body if set)

* over `body` if both are set)
* - `response` (a response callback result, either a response object or 'close',
* if provided this will be used as an immediately response, the request will
* not be passed through at all, and any beforeResponse callback will never
* fire)
*/
beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult>;
beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult | void> | void;
/**
* A callback that will be passed the full response before it is passed through,
* and which returns an object that defines how the the response content should
* before it's returned to the client.
* and which returns a value that defines how the the response content should
* be transformed before it's returned to the client.
*
* The callback should return an object that definies how the response
* should be changed. All fields on the object are optional. The possible
* fields are:
* 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). The possible fields are:
*
* - `status` (number, will replace the HTTP status code)

@@ -229,3 +233,3 @@ * - `headers` (object with string keys & values, replaces all headers if set)

*/
beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult>;
beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult | void> | void;
}

@@ -348,4 +352,4 @@ export interface RequestTransform {

readonly transformResponse?: ResponseTransform;
readonly beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult>;
readonly beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult>;
readonly beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult | void> | void;
readonly beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult | void> | void;
readonly lookupOptions?: PassThroughLookupOptions;

@@ -352,0 +356,0 @@ readonly proxyConfig?: ProxyConfig;

@@ -36,2 +36,3 @@ "use strict";

const http_agents_1 = require("../../util/http-agents");
const dns_1 = require("../../util/dns");
// An error that indicates that the handler is aborting the request.

@@ -111,3 +112,9 @@ // This could be intentional, or an upstream server aborting the request.

}
writeResponseFromCallback(outResponse, response);
if (outResponse === 'close') {
request.socket.end();
throw new AbortError('Connection closed (intentionally)');
}
else {
writeResponseFromCallback(outResponse, response);
}
});

@@ -122,3 +129,8 @@ }

const callbackResult = yield this.callback.call(null, request);
return serialization_1.withSerializedBodyBuffer(callbackResult);
if (typeof callbackResult === 'string') {
return callbackResult;
}
else {
return serialization_1.withSerializedBodyBuffer(callbackResult);
}
}));

@@ -129,7 +141,13 @@ return { type: this.type, name: this.callback.name, version: 2 };

const rpcCallback = (request) => __awaiter(this, void 0, void 0, function* () {
return serialization_1.withDeserializedBodyBuffer(yield channel.request({ args: [
const callbackResult = yield channel.request({ args: [
(version || -1) >= 2
? serialization_1.withSerializedBodyReader(request)
: request // Backward compat: old handlers
] }));
] });
if (typeof callbackResult === 'string') {
return callbackResult;
}
else {
return serialization_1.withDeserializedBodyBuffer(callbackResult);
}
});

@@ -465,16 +483,24 @@ // Pass across the name from the real callback, for explain()

lookup() {
if (!this.lookupOptions)
return undefined;
if (!this._cacheableLookupInstance) {
this._cacheableLookupInstance = new cacheable_lookup_1.default({
maxTtl: this.lookupOptions.maxTtl,
errorTtl: this.lookupOptions.errorTtl,
// As little caching of "use the fallback server" as possible:
fallbackDuration: 0
});
if (this.lookupOptions.servers) {
this._cacheableLookupInstance.servers = this.lookupOptions.servers;
if (!this.lookupOptions) {
if (!this._cacheableLookupInstance) {
// By default, use 10s caching of hostnames, just to reduce the delay from
// endlessly 10ms query delay for 'localhost' with every request.
this._cacheableLookupInstance = new dns_1.CachedDns(10000);
}
return this._cacheableLookupInstance.lookup;
}
return this._cacheableLookupInstance.lookup;
else {
if (!this._cacheableLookupInstance) {
this._cacheableLookupInstance = new cacheable_lookup_1.default({
maxTtl: this.lookupOptions.maxTtl,
errorTtl: this.lookupOptions.errorTtl,
// As little caching of "use the fallback server" as possible:
fallbackDuration: 0
});
if (this.lookupOptions.servers) {
this._cacheableLookupInstance.servers = this.lookupOptions.servers;
}
}
return this._cacheableLookupInstance.lookup;
}
}

@@ -576,30 +602,37 @@ explain() {

const modifiedReq = yield this.beforeRequest(Object.assign(Object.assign({}, completedRequest), { headers: _.clone(completedRequest.headers) }));
if (modifiedReq.response) {
// The callback has provided a full response: don't passthrough at all, just use it.
writeResponseFromCallback(modifiedReq.response, clientRes);
return;
if (modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.response) {
if (modifiedReq.response === 'close') {
const socket = clientReq.socket;
socket.end();
throw new AbortError('Connection closed (intentionally)');
}
else {
// The callback has provided a full response: don't passthrough at all, just use it.
writeResponseFromCallback(modifiedReq.response, clientRes);
return;
}
}
method = modifiedReq.method || method;
reqUrl = modifiedReq.url || reqUrl;
headers = modifiedReq.headers || headers;
method = (modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.method) || method;
reqUrl = (modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.url) || reqUrl;
headers = (modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.headers) || headers;
Object.assign(headers, isH2Downstream
? getCorrectPseudoheaders(reqUrl, clientReq.headers, modifiedReq.headers)
: { 'host': getCorrectHost(reqUrl, clientReq.headers, modifiedReq.headers) });
headersManuallyModified = !!modifiedReq.headers;
validateCustomHeaders(completedRequest.headers, modifiedReq.headers, OVERRIDABLE_REQUEST_PSEUDOHEADERS // These are handled by getCorrectPseudoheaders above
? getCorrectPseudoheaders(reqUrl, clientReq.headers, modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.headers)
: { 'host': getCorrectHost(reqUrl, clientReq.headers, modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.headers) });
headersManuallyModified = !!(modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.headers);
validateCustomHeaders(completedRequest.headers, modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.headers, OVERRIDABLE_REQUEST_PSEUDOHEADERS // These are handled by getCorrectPseudoheaders above
);
if (modifiedReq.json) {
if (modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.json) {
headers['content-type'] = 'application/json';
reqBodyOverride = buffer_utils_1.asBuffer(JSON.stringify(modifiedReq.json));
reqBodyOverride = buffer_utils_1.asBuffer(JSON.stringify(modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.json));
}
else {
reqBodyOverride = getCallbackResultBody(modifiedReq.body);
reqBodyOverride = getCallbackResultBody(modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.body);
}
if (reqBodyOverride !== undefined) {
headers['content-length'] = getCorrectContentLength(reqBodyOverride, clientReq.headers, modifiedReq.headers);
headers['content-length'] = getCorrectContentLength(reqBodyOverride, clientReq.headers, modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.headers);
}
headers = dropUndefinedValues(headers);
// Reparse the new URL, if necessary
if (modifiedReq.url) {
if (!request_utils_1.isAbsoluteUrl(modifiedReq.url))
if (modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.url) {
if (!request_utils_1.isAbsoluteUrl(modifiedReq === null || modifiedReq === void 0 ? void 0 : modifiedReq.url))
throw new Error("Overridden request URLs must be absolute");

@@ -736,18 +769,24 @@ ({ protocol, hostname, port, path } = url.parse(reqUrl));

});
validateCustomHeaders(cleanHeaders, modifiedRes.headers);
serverStatusCode = modifiedRes.statusCode ||
modifiedRes.status ||
if (modifiedRes === 'close') {
// Dump the real response data and kill the client socket:
serverRes.resume();
clientRes.socket.end();
throw new AbortError('Connection closed (intentionally)');
}
validateCustomHeaders(cleanHeaders, modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.headers);
serverStatusCode = (modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.statusCode) ||
(modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.status) ||
serverStatusCode;
serverStatusMessage = modifiedRes.statusMessage ||
serverStatusMessage = (modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.statusMessage) ||
serverStatusMessage;
serverHeaders = modifiedRes.headers || serverHeaders;
if (modifiedRes.json) {
serverHeaders = (modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.headers) || serverHeaders;
if (modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.json) {
serverHeaders['content-type'] = 'application/json';
resBodyOverride = buffer_utils_1.asBuffer(JSON.stringify(modifiedRes.json));
resBodyOverride = buffer_utils_1.asBuffer(JSON.stringify(modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.json));
}
else {
resBodyOverride = getCallbackResultBody(modifiedRes.body);
resBodyOverride = getCallbackResultBody(modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.body);
}
if (resBodyOverride !== undefined) {
serverHeaders['content-length'] = getCorrectContentLength(resBodyOverride, serverRes.headers, modifiedRes.headers, method === 'HEAD' // HEAD responses are allowed mismatched content-length
serverHeaders['content-length'] = getCorrectContentLength(resBodyOverride, serverRes.headers, modifiedRes === null || modifiedRes === void 0 ? void 0 : modifiedRes.headers, method === 'HEAD' // HEAD responses are allowed mismatched content-length
);

@@ -857,6 +896,8 @@ }

serverReq.flushHeaders();
// For similar reasons, we don't want any buffering on outgoing data at all if possible:
serverReq.setNoDelay(true);
}))().catch((e) => {
// Catch sync/await errors in the above promise:
// Catch otherwise-unhandled sync or async errors in the above promise:
if (serverReq)
serverReq.abort();
serverReq.destroy();
clientRes.tags.push('passthrough-error:' + e.code);

@@ -871,7 +912,10 @@ reject(e);

channel.onRequest('beforeRequest', (req) => __awaiter(this, void 0, void 0, function* () {
const result = serialization_1.withSerializedBodyBuffer(yield this.beforeRequest(serialization_1.withDeserializedBodyReader(req.args[0])));
if (result.response) {
result.response = serialization_1.withSerializedBodyBuffer(result.response);
const callbackResult = yield this.beforeRequest(serialization_1.withDeserializedBodyReader(req.args[0]));
const serializedResult = callbackResult
? serialization_1.withSerializedBodyBuffer(callbackResult)
: undefined;
if ((serializedResult === null || serializedResult === void 0 ? void 0 : serializedResult.response) && typeof (serializedResult === null || serializedResult === void 0 ? void 0 : serializedResult.response) !== 'string') {
serializedResult.response = serialization_1.withSerializedBodyBuffer(serializedResult.response);
}
return result;
return serializedResult;
}));

@@ -881,3 +925,12 @@ }

channel.onRequest('beforeResponse', (req) => __awaiter(this, void 0, void 0, function* () {
return serialization_1.withSerializedBodyBuffer(yield this.beforeResponse(serialization_1.withDeserializedBodyReader(req.args[0])));
const callbackResult = yield this.beforeResponse(serialization_1.withDeserializedBodyReader(req.args[0]));
if (typeof callbackResult === 'string') {
return callbackResult;
}
else if (callbackResult) {
return serialization_1.withSerializedBodyBuffer(callbackResult);
}
else {
return undefined;
}
}));

@@ -919,3 +972,3 @@ }

}));
if (result.response) {
if (result.response && typeof result.response !== 'string') {
result.response = serialization_1.withDeserializedBodyBuffer(result.response);

@@ -928,5 +981,11 @@ }

beforeResponse = (res) => __awaiter(this, void 0, void 0, function* () {
return serialization_1.withDeserializedBodyBuffer(yield channel.request('beforeResponse', {
const callbackResult = yield channel.request('beforeResponse', {
args: [serialization_1.withSerializedBodyReader(res)]
}));
});
if (callbackResult && typeof callbackResult !== 'string') {
return serialization_1.withDeserializedBodyBuffer(callbackResult);
}
else {
return callbackResult;
}
});

@@ -933,0 +992,0 @@ }

@@ -81,4 +81,8 @@ /**

*
* The callback should return a response object or a promise for one.
* The response object may include various fields to define the response.
* The callback should return a response object with the fields below, or
* the string 'close' to immediately close the connection. The callback
* can be asynchronous, in which case it should return this value wrapped
* in a promise.
*
* Responses object may include various fields to define the response.
* All fields are optional, with the defaults listed below.

@@ -103,3 +107,3 @@ *

*/
thenCallback(callback: (request: CompletedRequest) => MaybePromise<CallbackResponseResult>): Promise<MockedEndpoint>;
thenCallback(callback: (request: CompletedRequest) => MaybePromise<CallbackResponseResult | 'close'>): Promise<MockedEndpoint>;
/**

@@ -106,0 +110,0 @@ * Respond immediately with the given status (and optionally, headers),

@@ -103,4 +103,8 @@ "use strict";

*
* The callback should return a response object or a promise for one.
* The response object may include various fields to define the response.
* The callback should return a response object with the fields below, or
* the string 'close' to immediately close the connection. The callback
* can be asynchronous, in which case it should return this value wrapped
* in a promise.
*
* Responses object may include various fields to define the response.
* All fields are optional, with the defaults listed below.

@@ -107,0 +111,0 @@ *

@@ -173,2 +173,6 @@ "use strict";

socket.lastHopEncrypted = false;
// For actual sockets, set NODELAY to avoid any buffering whilst streaming. This is
// off by default in Node HTTP, but likely to be enabled soon & is default in curl.
if ('setNoDelay' in socket)
socket.setNoDelay(true);
});

@@ -175,0 +179,0 @@ server.on('secureConnection', (socket) => {

@@ -24,4 +24,5 @@ /**

*/
export default class MockttpServer extends AbstractMockttp implements Mockttp {
export declare class MockttpServer extends AbstractMockttp implements Mockttp {
private requestRules;
private fallbackRequestRule;
private webSocketRules;

@@ -46,2 +47,3 @@ private httpsOptions;

addRequestRules: (...ruleData: RequestRuleData[]) => Promise<ServerMockedEndpoint[]>;
setFallbackRequestRule: (ruleDatum: RequestRuleData) => Promise<ServerMockedEndpoint>;
setWebSocketRules: (...ruleData: WebSocketRuleData[]) => Promise<ServerMockedEndpoint[]>;

@@ -48,0 +50,0 @@ addWebSocketRules: (...ruleData: WebSocketRuleData[]) => Promise<ServerMockedEndpoint[]>;

@@ -15,2 +15,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.MockttpServer = void 0;
const url = require("url");

@@ -58,2 +59,12 @@ const tls = require("tls");

};
this.setFallbackRequestRule = (ruleDatum) => __awaiter(this, void 0, void 0, function* () {
if (this.fallbackRequestRule)
throw new Error('Only one fallback request rule can be registered at any time');
const hasNoMatchers = ruleDatum.matchers.length === 0 || (ruleDatum.matchers.length === 1 &&
ruleDatum.matchers[0].type === 'wildcard');
if (!hasNoMatchers)
throw new Error('Fallback request rules cannot include specific matching configuration');
this.fallbackRequestRule = new request_rule_1.RequestRule(ruleDatum);
return Promise.resolve(new mocked_endpoint_1.ServerMockedEndpoint(this.fallbackRequestRule));
});
this.setWebSocketRules = (...ruleData) => {

@@ -152,4 +163,7 @@ this.webSocketRules.forEach(r => r.dispose());

reset() {
var _a;
this.requestRules.forEach(r => r.dispose());
this.requestRules = [];
(_a = this.fallbackRequestRule) === null || _a === void 0 ? void 0 : _a.dispose();
this.fallbackRequestRule = undefined;
this.webSocketRules.forEach(r => r.dispose());

@@ -355,2 +369,5 @@ this.webSocketRules = [];

}
else if (this.fallbackRequestRule) {
yield this.fallbackRequestRule.handle(request, response, this.recordTraffic);
}
else {

@@ -454,3 +471,3 @@ yield this.sendUnmatchedRequestError(request, response);

console.warn(`Unmatched request received: ${requestExplanation}`);
return `No rules were found matching this request.");
return `No rules were found matching this request.
This request was: ${requestExplanation}

@@ -632,3 +649,3 @@

}
exports.default = MockttpServer;
exports.MockttpServer = MockttpServer;
//# sourceMappingURL=mockttp-server.js.map

@@ -19,3 +19,3 @@ /**

private server;
private mockServers;
private servers;
constructor(options?: StandaloneServerOptions);

@@ -27,8 +27,5 @@ private loadSchema;

}): Promise<void>;
private routers;
private subscriptionServers;
private streamServers;
private startMockServer;
stop(): Promise<void>;
get activeServerPorts(): number[];
get activeServerPorts(): string[];
}

@@ -35,0 +32,0 @@ export interface MockServerConfig {

@@ -68,6 +68,3 @@ "use strict";

this.server = null;
this.mockServers = [];
this.routers = {};
this.subscriptionServers = {};
this.streamServers = {};
this.servers = {};
this.debug = options.debug || false;

@@ -99,3 +96,3 @@ if (this.debug)

const mockServerOptions = _.defaults({}, req.body, options.serverDefaults);
if (_.isNumber(port) && this.routers[port] != null) {
if (_.isNumber(port) && this.servers[port] != null) {
res.status(409).json({

@@ -117,2 +114,13 @@ error: `Cannot start: mock server is already running on port ${port}`

}));
this.app.post('/reset', (req, res) => __awaiter(this, void 0, void 0, function* () {
if (this.debug)
console.log('Resetting standalone server');
try {
yield Promise.all(Object.values(this.servers).map(({ stop }) => stop()));
res.json({ success: true });
}
catch (e) {
res.status(500).json({ error: (e === null || e === void 0 ? void 0 : e.message) || 'Unknown error' });
}
}));
// Dynamically route to admin servers ourselves, so we can easily add/remove

@@ -122,3 +130,3 @@ // servers as we see fit later on.

const serverPort = Number(req.params.port);
const serverRouter = this.routers[serverPort];
const serverRouter = this.servers[serverPort].router;
if (!serverRouter) {

@@ -147,3 +155,3 @@ res.status(404).send('Unknown mock server');

this.server.on('upgrade', (req, socket, head) => __awaiter(this, void 0, void 0, function* () {
var _a;
var _a, _b;
const reqOrigin = req.headers['origin'];

@@ -160,5 +168,5 @@ if (this.requiredOrigin && !(yield strictOriginMatch(reqOrigin, this.requiredOrigin))) {

let port = parseInt(isMatch[1], 10);
let wsServer = isSubscriptionRequest ?
(_a = this.subscriptionServers[port]) === null || _a === void 0 ? void 0 : _a.wsServer :
this.streamServers[port];
let wsServer = isSubscriptionRequest
? (_b = (_a = this.servers[port]) === null || _a === void 0 ? void 0 : _a.subscriptionServer) === null || _b === void 0 ? void 0 : _b.wsServer
: this.servers[port].streamServer;
if (wsServer) {

@@ -184,3 +192,3 @@ wsServer.handleUpgrade(req, socket, head, (ws) => {

return __awaiter(this, void 0, void 0, function* () {
const mockServer = new mockttp_server_1.default(_.defaults(options, {
const mockServer = new mockttp_server_1.MockttpServer(_.defaults(options, {
// Use debug mode if the client requests it, or if the standalone has it set

@@ -190,6 +198,4 @@ debug: this.debug

yield mockServer.start(portConfig);
this.mockServers.push(mockServer);
const mockPort = mockServer.port;
const mockServerRouter = express.Router();
this.routers[mockPort] = mockServerRouter;
let running = true;

@@ -200,16 +206,12 @@ const stopServer = () => __awaiter(this, void 0, void 0, function* () {

running = false;
const server = this.servers[mockPort];
delete this.servers[mockPort];
yield mockServer.stop();
this.mockServers = _.reject(this.mockServers, mockServer);
delete this.routers[mockPort];
this.subscriptionServers[mockPort].close();
delete this.subscriptionServers[mockPort];
this.streamServers[mockPort].close();
this.streamServers[mockPort].emit('close');
delete this.streamServers[mockPort];
server.subscriptionServer.close();
server.streamServer.close();
server.streamServer.emit('close');
});
mockServerRouter.post('/stop', (req, res) => __awaiter(this, void 0, void 0, function* () {
yield stopServer();
res.status(200).send(JSON.stringify({
success: true
}));
res.json({ success: true });
}));

@@ -228,8 +230,8 @@ // A pair of sockets, representing the 2-way connection between the server & WSs.

}
this.streamServers[mockPort] = new ws.Server({ noServer: true });
this.streamServers[mockPort].on('connection', (ws) => {
const streamServer = new ws.Server({ noServer: true });
streamServer.on('connection', (ws) => {
let newClientStream = connectWebSocketStream(ws);
wsSocket.pipe(newClientStream).pipe(wsSocket, { end: false });
});
this.streamServers[mockPort].on('close', () => {
streamServer.on('close', () => {
wsSocket.end();

@@ -247,3 +249,3 @@ serverSocket.end();

const schema = yield this.loadSchema('schema.gql', mockServer, serverSocket);
this.subscriptionServers[mockPort] = subscriptions_transport_ws_1.SubscriptionServer.create({
const subscriptionServer = subscriptions_transport_ws_1.SubscriptionServer.create({
schema, execute: graphql_1.execute, subscribe: graphql_1.subscribe

@@ -254,2 +256,9 @@ }, {

mockServerRouter.use(express_graphql_1.graphqlHTTP({ schema }));
this.servers[mockPort] = {
mockServer,
router: mockServerRouter,
streamServer,
subscriptionServer,
stop: stopServer
};
return {

@@ -266,3 +275,3 @@ mockPort,

this.server.destroy(),
].concat(this.mockServers.map((s) => s.stop()))).then(() => {
].concat(Object.values(this.servers).map((s) => s.stop()))).then(() => {
this.server = null;

@@ -272,3 +281,3 @@ });

get activeServerPorts() {
return this.mockServers.map(s => s.port);
return Object.keys(this.servers);
}

@@ -275,0 +284,0 @@ }

@@ -7,3 +7,3 @@ /**

import type { IResolvers } from "@graphql-tools/utils/Interfaces";
import MockttpServer from "../server/mockttp-server";
import { MockttpServer } from "../server/mockttp-server";
export declare function buildStandaloneModel(mockServer: MockttpServer, stream: Duplex): IResolvers;

@@ -132,2 +132,9 @@ "use strict";

}),
Void: new graphql_1.GraphQLScalarType({
name: 'Void',
description: 'Nothing at all',
serialize: (value) => null,
parseValue: (input) => null,
parseLiteral: () => { throw new Error('Void literals are not supported'); }
}),
Buffer: new graphql_1.GraphQLScalarType({

@@ -202,2 +209,5 @@ name: 'Buffer',

}),
setFallbackRule: (__, { input }) => __awaiter(this, void 0, void 0, function* () {
return mockServer.setFallbackRequestRule(rule_serialization_1.deserializeRuleData(input, stream));
}),
addWebSocketRule: (__, { input }) => __awaiter(this, void 0, void 0, function* () {

@@ -212,6 +222,3 @@ return mockServer.addWebSocketRule(rule_serialization_1.deserializeWebSocketRuleData(input, stream));

}),
reset: () => {
mockServer.reset();
return true;
}
reset: () => mockServer.reset()
}, Subscription: {

@@ -218,0 +225,0 @@ requestInitiated: {

{
"name": "mockttp",
"version": "2.0.1",
"version": "2.1.0",
"description": "Mock HTTP server for testing HTTP clients and stubbing webservices",

@@ -5,0 +5,0 @@ "main": "dist/main.js",

@@ -80,2 +80,8 @@ /**

/**
* The full URL to use for a standalone server with remote (or local but browser) client.
* When using a local server, this parameter is ignored.
*/
standaloneServerUrl?: string;
/**
* Options to include on all client requests, e.g. to add extra

@@ -133,3 +139,75 @@ * headers for authentication.

async function requestFromStandalone<T>(serverUrl: string, path: string, options?: RequestInit): Promise<T> {
const url = `${serverUrl}${path}`;
let response;
try {
response = await fetch(url, options);
} catch (e) {
if (e.code === 'ECONNREFUSED') {
throw new ConnectionError(`Failed to connect to standalone server at ${serverUrl}`);
} else throw e;
}
if (response.status >= 400) {
let body = await response.text();
let jsonBody: { error?: string } | null = null;
try {
jsonBody = JSON.parse(body);
} catch (e) { }
if (jsonBody && jsonBody.error) {
throw new RequestError(
jsonBody.error,
response
);
} else {
throw new RequestError(
`Request to ${url} failed, with status ${response.status} and response body: ${body}`,
response
);
}
} else {
return response.json();
}
}
/**
* Reset a remote standalone server, shutting down all Mockttp servers controlled by that
* standalone server. This is equivalent to calling `client.stop()` for all remote
* clients of the target server.
*
* This can be useful in some rare cases, where a client might fail to reliably tear down
* its own server, e.g. in Cypress testing. In this case, it's useful to reset the
* standalone server completely remotely without needing access to any previous client
* instances, to ensure all servers from previous test runs have been shut down.
*
* After this is called, behaviour of any previously connected clients is undefined, and
* it's likely that they may throw errors or experience other undefined behaviour. Ensure
* that `client.stop()` has been called on all active clients before calling this method.
*/
export async function resetStandalone(options: {
/**
* The full URL to use for a standalone server with remote (or local but browser) client.
* When using a local server, this parameter is ignored.
*/
standaloneServerUrl?: string;
/**
* Options to include on all client requests, e.g. to add extra
* headers for authentication.
*/
client?: {
headers?: { [key: string]: string };
}
} = {}): Promise<void> {
const serverUrl = options.standaloneServerUrl || `http://localhost:${DEFAULT_STANDALONE_PORT}`;
await requestFromStandalone(serverUrl, '/reset', {
...options.client,
method: 'POST'
});
}
/**
* A Mockttp implementation, controlling a remote Mockttp standalone server.

@@ -140,3 +218,3 @@ *

*/
export default class MockttpClient extends AbstractMockttp implements Mockttp {
export class MockttpClient extends AbstractMockttp implements Mockttp {

@@ -147,5 +225,4 @@ private mockServerOptions: RequireProps<MockttpClientOptions, 'cors' | 'standaloneServerUrl'>;

private mockServerConfig: MockServerConfig | undefined;
private mockServerSchema: any;
private mockServerStream: Duplex | undefined;
private mockServerSchema: any;
private subscriptionClient: SubscriptionClient | undefined;

@@ -164,3 +241,3 @@

this.mockServerOptions = _.omit(options, 'client') as RequireProps<
MockttpOptions, 'cors' | 'standaloneServerUrl'
MockttpClientOptions, 'cors' | 'standaloneServerUrl'
>

@@ -170,38 +247,2 @@ this.mockClientOptions = options.client || {};

private async requestFromStandalone<T>(path: string, options?: RequestInit): Promise<T> {
const url = `${this.mockServerOptions.standaloneServerUrl}${path}`;
let response;
try {
response = await fetch(url, mergeClientOptions(options, this.mockClientOptions));
} catch (e) {
if (e.code === 'ECONNREFUSED') {
throw new ConnectionError(`Failed to connect to standalone server at ${this.mockServerOptions.standaloneServerUrl}`);
} else throw e;
}
if (response.status >= 400) {
let body = await response.text();
let jsonBody: { error?: string } | null = null;
try {
jsonBody = JSON.parse(body);
} catch (e) { }
if (jsonBody && jsonBody.error) {
throw new RequestError(
jsonBody.error,
response
);
} else {
throw new RequestError(
`Request to ${url} failed, with status ${response.status} and response body: ${body}`,
response
);
}
} else {
return response.json();
}
}
private openStreamToMockServer(config: MockServerConfig): Promise<Duplex> {

@@ -214,2 +255,5 @@ const standaloneStreamServer = this.mockServerOptions.standaloneServerUrl.replace(/^http/, 'ws');

// When this stream closes, the server is gone and need to shut down.
stream.on('close', () => this.completeShutdown());
return new Promise((resolve, reject) => {

@@ -291,9 +335,13 @@ stream.once('connect', () => resolve(stream));

const path = portConfig ? `/start?port=${JSON.stringify(portConfig)}` : '/start';
let mockServerConfig = await this.requestFromStandalone<MockServerConfig>(path, {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify(this.mockServerOptions)
});
let mockServerConfig = await requestFromStandalone<MockServerConfig>(
this.mockServerOptions.standaloneServerUrl,
path,
mergeClientOptions({
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify(this.mockServerOptions)
}, this.mockClientOptions)
);

@@ -322,5 +370,22 @@ // Also open a stream connection, for 2-way communication we might need later.

this.mockServerConfig = this.mockServerStream = undefined;
this.mockServerStream = undefined;
this.mockServerSchema = undefined;
this.subscriptionClient = undefined;
this.mockServerConfig = undefined;
}
// Called when the remote server appears to have closed: shut down directly,
// no need to close things up nicely.
private completeShutdown(): void {
if (!this.mockServerConfig) return;
try { this.mockServerStream!.end(); } catch (e) { }
try { this.subscriptionClient!.close(); } catch (e) { }
this.mockServerStream = undefined;
this.mockServerSchema = undefined;
this.subscriptionClient = undefined;
this.mockServerConfig = undefined;
}
private typeHasField(typeName: string, fieldName: string): boolean {

@@ -348,6 +413,6 @@ const type: any = _.find(this.mockServerSchema.types, { name: typeName });

reset = async (): Promise<boolean> => {
return (await this.queryMockServer<boolean>(
reset = async (): Promise<void> => {
return (await this.queryMockServer<void>(
`mutation Reset {
reset
reset
}`

@@ -370,7 +435,7 @@ ));

public addRequestRules = async (...rules: RequestRuleData[]): Promise<MockedEndpoint[]> => {
return this._addRules(rules, false);
return this._addRequestRules(rules, false);
}
public setRequestRules = async (...rules: RequestRuleData[]): Promise<MockedEndpoint[]> => {
return this._addRules(rules, true);
return this._addRequestRules(rules, true);
}

@@ -386,3 +451,3 @@

private _addRules = async (
private _addRequestRules = async (
rules: Array<RequestRuleData>,

@@ -411,3 +476,3 @@ reset: boolean

let endpoints = (await this.queryMockServer<{ endpoints: Array<{ id: string, explanation?: string }> }>(
let { endpoints } = (await this.queryMockServer<{ endpoints: Array<{ id: string, explanation?: string }> }>(
`mutation ${requestName}($newRules: [MockRule!]!) {

@@ -427,3 +492,3 @@ endpoints: ${mutationName}(input: $newRules) {

}
)).endpoints;
));

@@ -435,2 +500,21 @@ return endpoints.map(({ id, explanation }) =>

setFallbackRequestRule = async (
rule: RequestRuleData
): Promise<MockedEndpoint> => {
if (!this.mockServerConfig) throw new Error('Cannot add rules before the server is started');
let { endpoint: { id, explanation } } = (await this.queryMockServer<{ endpoint: { id: string, explanation: string } }>(
`mutation SetFallbackRule($fallbackRule: MockRule!) {
endpoint: setFallbackRule(input: $fallbackRule) {
id,
explanation
}
}`, {
fallbackRule: serializeRuleData(rule, this.mockServerStream!)
}
));
return new MockedEndpointClient(id, explanation, this.getEndpointDataGetter(id));
}
private _addWsRules = async (

@@ -437,0 +521,0 @@ rules: Array<WebSocketRuleData>,

@@ -1,2 +0,2 @@

import MockttpClient from "./client/mockttp-client";
import { MockttpClient, resetStandalone } from "./client/mockttp-client";

@@ -26,2 +26,3 @@ import { Mockttp, MockttpOptions } from "./mockttp";

throw new Error('Cannot set up a standalone server within a browser');
}
}
export { resetStandalone };

@@ -5,4 +5,4 @@ /**

import MockttpServer from "./server/mockttp-server";
import MockttpClient, { MockttpClientOptions } from "./client/mockttp-client";
import { MockttpServer } from "./server/mockttp-server";
import { MockttpClient, MockttpClientOptions, resetStandalone } from "./client/mockttp-client";
import { MockttpStandalone, StandaloneServerOptions } from "./standalone/mockttp-standalone";

@@ -77,1 +77,3 @@

}
export { resetStandalone };

@@ -117,2 +117,14 @@ /**

/**
* Get a builder for a fallback mock rule that will match any unmatched requests
* on any path. A fallback rule will only match if there is no existing rule at
* all matching the request, or all existing rules have an explicit limit (like
* `once()`) that has been completed.
*
* Only one unmatched request rule can be registered, and it cannot include any
* matchers. In either of these cases, when the final `thenX()` method is called,
* a rejected promise will be returned.
*/
unmatchedRequest(): RequestRuleBuilder;
/**
* Get a builder for a mock rule that will match GET requests for the given path.

@@ -469,8 +481,2 @@ * If no path is specified, this matches all GET requests.

/**
* The full URL to use for a standalone server with remote (or local but browser) client.
* When using a local server, this parameter is ignored.
*/
standaloneServerUrl?: string;
/**
* By default, requests that match no rules will receive an explanation of the

@@ -568,2 +574,3 @@ * request & existing rules, followed by some suggested example Mockttp code

abstract setRequestRules(...ruleData: RequestRuleData[]): Promise<MockedEndpoint[]>;
abstract setFallbackRequestRule(ruleData: RequestRuleData): Promise<MockedEndpoint>;

@@ -585,2 +592,6 @@ // Deprecated endpoints for backward compat:

unmatchedRequest(): RequestRuleBuilder {
return new RequestRuleBuilder(this.setFallbackRequestRule);
}
get(url?: string | RegExp): RequestRuleBuilder {

@@ -587,0 +598,0 @@ return new RequestRuleBuilder(Method.GET, url, this.addRequestRule);

@@ -166,4 +166,8 @@ /**

*
* The callback should return a response object or a promise for one.
* The response object may include various fields to define the response.
* The callback should return a response object with the fields below, or
* the string 'close' to immediately close the connection. The callback
* can be asynchronous, in which case it should return this value wrapped
* in a promise.
*
* Responses object may include various fields to define the response.
* All fields are optional, with the defaults listed below.

@@ -189,3 +193,3 @@ *

thenCallback(callback:
(request: CompletedRequest) => MaybePromise<CallbackResponseResult>
(request: CompletedRequest) => MaybePromise<CallbackResponseResult | 'close'>
): Promise<MockedEndpoint> {

@@ -192,0 +196,0 @@ const rule: RequestRuleData = {

@@ -186,3 +186,3 @@ import _ = require('lodash');

server.on('connection', (socket: net.Socket) => {
server.on('connection', (socket: net.Socket | http2.ServerHttp2Stream) => {
socket.__timingInfo = socket.__timingInfo || buildTimingInfo();

@@ -193,2 +193,6 @@

socket.lastHopEncrypted = false;
// For actual sockets, set NODELAY to avoid any buffering whilst streaming. This is
// off by default in Node HTTP, but likely to be enabled soon & is default in curl.
if ('setNoDelay' in socket) socket.setNoDelay(true);
});

@@ -195,0 +199,0 @@

@@ -66,5 +66,7 @@ /**

*/
export default class MockttpServer extends AbstractMockttp implements Mockttp {
export class MockttpServer extends AbstractMockttp implements Mockttp {
private requestRules: RequestRule[] = [];
private fallbackRequestRule: RequestRule | undefined;
private webSocketRules: WebSocketRule[] = [];

@@ -182,2 +184,6 @@

this.requestRules = [];
this.fallbackRequestRule?.dispose();
this.fallbackRequestRule = undefined;
this.webSocketRules.forEach(r => r.dispose());

@@ -225,2 +231,20 @@ this.webSocketRules = [];

public setFallbackRequestRule = async (ruleDatum: RequestRuleData): Promise<ServerMockedEndpoint> => {
if (this.fallbackRequestRule) throw new Error(
'Only one fallback request rule can be registered at any time'
);
const hasNoMatchers = ruleDatum.matchers.length === 0 || (
ruleDatum.matchers.length === 1 &&
ruleDatum.matchers[0].type === 'wildcard'
);
if (!hasNoMatchers) throw new Error(
'Fallback request rules cannot include specific matching configuration'
);
this.fallbackRequestRule = new RequestRule(ruleDatum);
return Promise.resolve(new ServerMockedEndpoint(this.fallbackRequestRule));
}
public setWebSocketRules = (...ruleData: WebSocketRuleData[]): Promise<ServerMockedEndpoint[]> => {

@@ -437,2 +461,4 @@ this.webSocketRules.forEach(r => r.dispose());

await nextRule.handle(request, response, this.recordTraffic);
} else if (this.fallbackRequestRule) {
await this.fallbackRequestRule.handle(request, response, this.recordTraffic);
} else {

@@ -534,3 +560,3 @@ await this.sendUnmatchedRequestError(request, response);

return `No rules were found matching this request.");
return `No rules were found matching this request.
This request was: ${requestExplanation}

@@ -537,0 +563,0 @@

@@ -27,3 +27,3 @@ /**

import { destroyable, DestroyableServer } from "../util/destroyable-server";
import MockttpServer from "../server/mockttp-server";
import { MockttpServer } from "../server/mockttp-server";
import { buildStandaloneModel } from "./standalone-model";

@@ -81,4 +81,11 @@ import { DEFAULT_STANDALONE_PORT } from '../types';

private mockServers: MockttpServer[] = [];
private servers: { [port: number]: {
router: express.Router,
stop: () => Promise<void>,
mockServer: MockttpServer,
subscriptionServer: SubscriptionServer,
streamServer: ws.Server
} } = { };
constructor(options: StandaloneServerOptions = {}) {

@@ -120,3 +127,3 @@ this.debug = options.debug || false;

if (_.isNumber(port) && this.routers[port] != null) {
if (_.isNumber(port) && this.servers[port] != null) {
res.status(409).json({

@@ -141,2 +148,16 @@ error: `Cannot start: mock server is already running on port ${port}`

this.app.post('/reset', async (req, res) => {
if (this.debug) console.log('Resetting standalone server');
try {
await Promise.all(
Object.values(this.servers).map(({ stop }) => stop())
);
res.json({ success: true });
} catch (e) {
res.status(500).json({ error: e?.message || 'Unknown error' });
}
});
// Dynamically route to admin servers ourselves, so we can easily add/remove

@@ -146,3 +167,3 @@ // servers as we see fit later on.

const serverPort = Number(req.params.port);
const serverRouter = this.routers[serverPort];
const serverRouter = this.servers[serverPort].router;

@@ -195,5 +216,5 @@ if (!serverRouter) {

let wsServer: ws.Server = isSubscriptionRequest ?
(<any> this.subscriptionServers[port])?.wsServer :
this.streamServers[port];
let wsServer: ws.Server = isSubscriptionRequest
? (<any> this.servers[port]?.subscriptionServer)?.wsServer
: this.servers[port].streamServer;

@@ -216,6 +237,2 @@ if (wsServer) {

private routers: { [port: number]: express.Router } = { };
private subscriptionServers: { [port: number]: SubscriptionServer } = { };
private streamServers: { [port: number]: ws.Server } = { };
private async startMockServer(options: MockttpOptions, portConfig?: number | PortRange): Promise<{

@@ -229,4 +246,4 @@ mockPort: number,

}));
await mockServer.start(portConfig);
this.mockServers.push(mockServer);

@@ -236,3 +253,2 @@ const mockPort = mockServer.port!;

const mockServerRouter = express.Router();
this.routers[mockPort] = mockServerRouter;

@@ -244,13 +260,9 @@ let running = true;

const server = this.servers[mockPort];
delete this.servers[mockPort];
await mockServer.stop();
this.mockServers = _.reject(this.mockServers, mockServer);
delete this.routers[mockPort];
this.subscriptionServers[mockPort].close();
delete this.subscriptionServers[mockPort];
this.streamServers[mockPort].close();
this.streamServers[mockPort].emit('close');
delete this.streamServers[mockPort];
server.subscriptionServer.close();
server.streamServer.close();
server.streamServer.emit('close');
};

@@ -260,5 +272,3 @@

await stopServer();
res.status(200).send(JSON.stringify({
success: true
}));
res.json({ success: true });
});

@@ -280,8 +290,8 @@

this.streamServers[mockPort] = new ws.Server({ noServer: true });
this.streamServers[mockPort].on('connection', (ws: WebSocket) => {
const streamServer = new ws.Server({ noServer: true });
streamServer.on('connection', (ws: WebSocket) => {
let newClientStream = connectWebSocketStream(ws);
wsSocket.pipe(newClientStream).pipe(wsSocket, { end: false });
});
this.streamServers[mockPort].on('close', () => {
streamServer.on('close', () => {
wsSocket.end();

@@ -302,3 +312,3 @@ serverSocket.end();

this.subscriptionServers[mockPort] = SubscriptionServer.create({
const subscriptionServer = SubscriptionServer.create({
schema, execute, subscribe

@@ -311,2 +321,10 @@ }, {

this.servers[mockPort] = {
mockServer,
router: mockServerRouter,
streamServer,
subscriptionServer,
stop: stopServer
};
return {

@@ -324,3 +342,3 @@ mockPort,

].concat(
this.mockServers.map((s) => s.stop())
Object.values(this.servers).map((s) => s.stop())
)).then(() => {

@@ -332,3 +350,3 @@ this.server = null;

get activeServerPorts() {
return this.mockServers.map(s => s.port);
return Object.keys(this.servers);
}

@@ -335,0 +353,0 @@ }

@@ -17,3 +17,3 @@ /**

import MockttpServer from "../server/mockttp-server";
import { MockttpServer } from "../server/mockttp-server";
import { ServerMockedEndpoint } from "../server/mocked-endpoint";

@@ -145,2 +145,10 @@ import { MockedEndpoint, MockedEndpointData, CompletedRequest, CompletedResponse, ClientError } from "../types";

Void: new GraphQLScalarType({
name: 'Void',
description: 'Nothing at all',
serialize: (value: any) => null,
parseValue: (input: string): any => null,
parseLiteral: (): any => { throw new Error('Void literals are not supported') }
}),
Buffer: new GraphQLScalarType({

@@ -233,2 +241,5 @@ name: 'Buffer',

},
setFallbackRule: async (__: any, { input }: { input: Serialized<RequestRuleData> }) => {
return mockServer.setFallbackRequestRule(deserializeRuleData(input, stream));
},

@@ -249,6 +260,3 @@ addWebSocketRule: async (__: any, { input }: { input: Serialized<WebSocketRuleData> }) => {

reset: () => {
mockServer.reset();
return true;
}
reset: () => mockServer.reset()
},

@@ -255,0 +263,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc