Socket
Socket
Sign inDemoInstall

@kronoslive/mockttp

Package Overview
Dependencies
130
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.5.2 to 0.7.2

custom-typings/Function.d.ts

3

dist/client/mocked-endpoint-client.d.ts

@@ -0,1 +1,4 @@

/**
* @module Internal
*/
import { MockedEndpointData, MockedEndpoint, CompletedRequest } from "../types";

@@ -2,0 +5,0 @@ export declare class MockedEndpointClient implements MockedEndpoint {

"use strict";
/**
* @module Internal
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -3,0 +6,0 @@ return new (P || (P = Promise))(function (resolve, reject) {

@@ -0,1 +1,4 @@

/**
* @module Mockttp
*/
import TypedError = require('typed-error');

@@ -19,2 +22,8 @@ import { MockedEndpoint, OngoingRequest } from "../types";

}
/**
* A Mockttp implementation, controlling a remote Mockttp standalone server.
*
* This starts servers by making requests to the remote standalone server, and exposes
* methods to directly manage them.
*/
export default class MockttpClient extends AbstractMockttp implements Mockttp {

@@ -21,0 +30,0 @@ private readonly standaloneServerUrl;

"use strict";
/**
* @module Mockttp
*/
var __extends = (this && this.__extends) || (function () {

@@ -53,3 +56,7 @@ var extendStatics = Object.setPrototypeOf ||

var subscriptions_transport_ws_1 = require("subscriptions-transport-ws");
var _a = getFetch(), fetch = _a.fetch, Headers = _a.Headers;
var _a = getFetch(),
/** @hidden */
fetch = _a.fetch,
/** @hidden */
Headers = _a.Headers;
var mockttp_1 = require("../mockttp");

@@ -87,2 +94,8 @@ var mock_rule_1 = require("../rules/mock-rule");

exports.GraphQLError = GraphQLError;
/**
* A Mockttp implementation, controlling a remote Mockttp standalone server.
*
* This starts servers by making requests to the remote standalone server, and exposes
* methods to directly manage them.
*/
var MockttpClient = /** @class */ (function (_super) {

@@ -89,0 +102,0 @@ __extends(MockttpClient, _super);

@@ -8,4 +8,33 @@ import { MockttpStandalone, StandaloneServerOptions } from "./standalone/mockttp-standalone";

export { Mockttp };
/**
* Get a Mockttp instance on the local machine.
*
* In most simple environments, you can call this method directly and immediately
* get a Mockttp instance and start mocking servers.
*
* In node, the mocked servers will run in process and require no further setup.
*
* In browsers this is an alias for getRemote. You'll need to start a standalone server
* outside your tests before calling this, which will create and manage your fake servers
* outside the browser.
*/
export declare function getLocal(options?: MockttpOptions): Mockttp;
/**
* Get a Mockttp instance, controlled through a Mockttp standalone server.
*
* This connects to a Mockttp standalone server, and uses that to start
* and stop mock servers.
*/
export declare function getRemote(options?: MockttpOptions): Mockttp;
/**
* Get a standalone server, which can be used remotely to create & manage mock servers.
*
* This function exists so you can set up these servers programmatically, but for most
* usage you can just run your tests via the `mockttp` binary, which will automatically
* start and stop a standalone server for you:
*
* ```
* mockttp -c <your test command>
* ```
*/
export declare function getStandalone(options?: StandaloneServerOptions): MockttpStandalone;
"use strict";
/**
* @module Mockttp
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -6,2 +9,14 @@ var mockttp_server_1 = require("./server/mockttp-server");

var mockttp_standalone_1 = require("./standalone/mockttp-standalone");
/**
* Get a Mockttp instance on the local machine.
*
* In most simple environments, you can call this method directly and immediately
* get a Mockttp instance and start mocking servers.
*
* In node, the mocked servers will run in process and require no further setup.
*
* In browsers this is an alias for getRemote. You'll need to start a standalone server
* outside your tests before calling this, which will create and manage your fake servers
* outside the browser.
*/
function getLocal(options) {

@@ -12,2 +27,8 @@ if (options === void 0) { options = {}; }

exports.getLocal = getLocal;
/**
* Get a Mockttp instance, controlled through a Mockttp standalone server.
*
* This connects to a Mockttp standalone server, and uses that to start
* and stop mock servers.
*/
function getRemote(options) {

@@ -18,2 +39,13 @@ if (options === void 0) { options = {}; }

exports.getRemote = getRemote;
/**
* Get a standalone server, which can be used remotely to create & manage mock servers.
*
* This function exists so you can set up these servers programmatically, but for most
* usage you can just run your tests via the `mockttp` binary, which will automatically
* start and stop a standalone server for you:
*
* ```
* mockttp -c <your test command>
* ```
*/
function getStandalone(options) {

@@ -20,0 +52,0 @@ if (options === void 0) { options = {}; }

@@ -1,21 +0,114 @@

import PartialMockRule from "./rules/partial-mock-rule";
/**
* @module Mockttp
*/
import MockRuleBuilder from "./rules/mock-rule-builder";
import { ProxyConfig, MockedEndpoint, OngoingRequest } from "./types";
import { MockRuleData } from "./rules/mock-rule-types";
import { MockRuleData, MockRuleCtx } from "./rules/mock-rule-types";
import { CAOptions } from './util/tls';
/**
* A mockttp instance allow you to start and stop mock servers and control their behaviour.
*
* Call `.start()` to set up a server on a random port, use methods like `.get(url)`,
* `.post(url)` and `.anyRequest()` to get a {@link MockRuleBuilder} and start defining
* mock rules. Call `.stop()` when your test is complete.
*/
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.
*/
start(port?: number): Promise<void>;
/**
* Stop the mock server and reset the rules.
*/
stop(): Promise<void>;
/**
* Enable extra debug output so you can understand exactly what the server is doing.
*/
enableDebug(): void;
/**
* Reset the stored rules. Most of the time it's better to start & stop the server instead,
* but this can be useful in some special cases.
*/
reset(): void;
/**
* The root URL of the server.
*
* This will throw an error if read before the server is started.
*/
url: string;
/**
* The URL for a given path on the server.
*
* This will throw an error if read before the server is started.
*/
urlFor(path: string): string;
/**
* The port the server is running on.
*
* This will throw an error if read before the server is started.
*/
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)
* ```
*/
proxyEnv: ProxyConfig;
urlFor(path: string): string;
anyRequest(): PartialMockRule;
get(url: string): PartialMockRule;
post(url: string): PartialMockRule;
put(url: string): PartialMockRule;
delete(url: string): PartialMockRule;
patch(url: string): PartialMockRule;
options(url: string): PartialMockRule;
/**
* Get a builder for a mock rule that will match any requests on any path.
*/
anyRequest(): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match GET requests for the given path.
*/
get(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match POST requests for the given path.
*/
post(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match PUT requests for the given path.
*/
put(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match DELETE requests for the given path.
*/
delete(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match PATCH requests for the given path.
*/
patch(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match OPTIONS requests for the given path.
*
* 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.
*/
options(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Subscribe to hear about request details as they're 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.
*/
on(event: 'request', callback: (req: OngoingRequest) => void): Promise<void>;

@@ -28,2 +121,5 @@ }

}
/**
* @hidden
*/
export declare abstract class AbstractMockttp {

@@ -38,9 +134,9 @@ protected cors: boolean;

urlFor(path: string): string;
anyRequest(): PartialMockRule;
get(url: string): PartialMockRule;
post(url: string): PartialMockRule;
put(url: string): PartialMockRule;
delete(url: string): PartialMockRule;
patch(url: string): PartialMockRule;
options(url: string): PartialMockRule;
anyRequest(): MockRuleBuilder;
get(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
post(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
put(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
delete(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
patch(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
options(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
}

34

dist/mockttp.js
"use strict";
/**
* @module Mockttp
*/
Object.defineProperty(exports, "__esModule", { value: true });
var partial_mock_rule_1 = require("./rules/partial-mock-rule");
var mock_rule_builder_1 = require("./rules/mock-rule-builder");
var types_1 = require("./types");
/**
* @hidden
*/
var AbstractMockttp = /** @class */ (function () {

@@ -24,24 +30,24 @@ function AbstractMockttp(options) {

AbstractMockttp.prototype.anyRequest = function () {
return new partial_mock_rule_1.default(this.addRule);
return new mock_rule_builder_1.default(this.addRule);
};
AbstractMockttp.prototype.get = function (url) {
return new partial_mock_rule_1.default(types_1.Method.GET, url, this.addRule);
AbstractMockttp.prototype.get = function (url, ctx) {
return new mock_rule_builder_1.default(types_1.Method.GET, url, this.addRule, ctx);
};
AbstractMockttp.prototype.post = function (url) {
return new partial_mock_rule_1.default(types_1.Method.POST, url, this.addRule);
AbstractMockttp.prototype.post = function (url, ctx) {
return new mock_rule_builder_1.default(types_1.Method.POST, url, this.addRule, ctx);
};
AbstractMockttp.prototype.put = function (url) {
return new partial_mock_rule_1.default(types_1.Method.PUT, url, this.addRule);
AbstractMockttp.prototype.put = function (url, ctx) {
return new mock_rule_builder_1.default(types_1.Method.PUT, url, this.addRule, ctx);
};
AbstractMockttp.prototype.delete = function (url) {
return new partial_mock_rule_1.default(types_1.Method.DELETE, url, this.addRule);
AbstractMockttp.prototype.delete = function (url, ctx) {
return new mock_rule_builder_1.default(types_1.Method.DELETE, url, this.addRule, ctx);
};
AbstractMockttp.prototype.patch = function (url) {
return new partial_mock_rule_1.default(types_1.Method.PATCH, url, this.addRule);
AbstractMockttp.prototype.patch = function (url, ctx) {
return new mock_rule_builder_1.default(types_1.Method.PATCH, url, this.addRule, ctx);
};
AbstractMockttp.prototype.options = function (url) {
AbstractMockttp.prototype.options = function (url, ctx) {
if (this.cors) {
throw new Error("Cannot mock OPTIONS requests with CORS enabled.\n\nYou 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 partial_mock_rule_1.default(types_1.Method.OPTIONS, url, this.addRule);
return new mock_rule_builder_1.default(types_1.Method.OPTIONS, url, this.addRule, ctx);
};

@@ -48,0 +54,0 @@ return AbstractMockttp;

@@ -0,1 +1,4 @@

/**
* @module MockRuleData
*/
import { RuleCompletionChecker } from './mock-rule-types';

@@ -2,0 +5,0 @@ export declare type CompletionCheckerData = (AlwaysData | OnceData | TwiceData | ThriceData | TimesData);

"use strict";
/**
* @module MockRuleData
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +6,0 @@ var AlwaysData = /** @class */ (function () {

/// <reference types="node" />
import http = require('http');
import { CompletedRequest } from "../types";
import { RequestHandler } from "./mock-rule-types";

@@ -18,6 +19,14 @@ export declare type HandlerData = (SimpleHandlerData | CallbackHandlerData | PassThroughHandlerData);

}
export interface CallbackHandlerResult {
status?: number;
json?: any;
body?: string;
headers?: {
[key: string]: string;
};
}
export declare class CallbackHandlerData {
callback: Function;
callback: (request: CompletedRequest) => CallbackHandlerResult;
readonly type: 'callback';
constructor(callback: Function);
constructor(callback: (request: CompletedRequest) => CallbackHandlerResult);
}

@@ -24,0 +33,0 @@ export declare class PassThroughHandlerData {

"use strict";
/**
* @module MockRuleData
*/
var __assign = (this && this.__assign) || Object.assign || function(t) {

@@ -50,2 +53,3 @@ for (var s, i = 1, n = arguments.length; i < n; i++) {

var https = require("https");
var request_utils_1 = require("../util/request-utils");
var SimpleHandlerData = /** @class */ (function () {

@@ -94,3 +98,3 @@ function SimpleHandlerData(status, data, headers) {

});
}, { explain: function () { return "respond with status " + status + (data ? " and body \"" + data + "\"" : ""); } });
}, { explain: function () { return "respond with status " + status + (headers ? ", headers " + JSON.stringify(headers) : "") + (data ? " and body \"" + data + "\"" : ""); } });
return responder;

@@ -102,70 +106,27 @@ },

return __awaiter(this, void 0, void 0, function () {
var buffer, text, json, formData, err_1, err_2, err_3, err_4, cleanRequest, ourResponse, err_5, defaultResponse;
var req, outResponse, error_1, defaultResponse;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, request.body.asBuffer()];
case 0: return [4 /*yield*/, request_utils_1.waitForCompletedRequest(request)];
case 1:
buffer = _a.sent();
return [3 /*break*/, 3];
req = _a.sent();
_a.label = 2;
case 2:
err_1 = _a.sent();
buffer = undefined;
return [3 /*break*/, 3];
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, callback(req)];
case 3:
_a.trys.push([3, 5, , 6]);
return [4 /*yield*/, request.body.asText()];
outResponse = _a.sent();
return [3 /*break*/, 5];
case 4:
text = _a.sent();
return [3 /*break*/, 6];
error_1 = _a.sent();
response.writeHead(500, 'Callback handler threw an exception');
response.end(error_1.toString());
return [2 /*return*/];
case 5:
err_2 = _a.sent();
text = undefined;
return [3 /*break*/, 6];
case 6:
_a.trys.push([6, 8, , 9]);
return [4 /*yield*/, request.body.asJson()];
case 7:
json = _a.sent();
return [3 /*break*/, 9];
case 8:
err_3 = _a.sent();
json = undefined;
return [3 /*break*/, 9];
case 9:
_a.trys.push([9, 11, , 12]);
return [4 /*yield*/, request.body.asFormData()];
case 10:
formData = _a.sent();
return [3 /*break*/, 12];
case 11:
err_4 = _a.sent();
formData = undefined;
return [3 /*break*/, 12];
case 12:
cleanRequest = {
protocol: request.protocol,
method: request.method,
url: request.url,
hostname: request.hostname,
path: request.path,
headers: request.headers,
body: { buffer: buffer, text: text, json: json, formData: formData }
};
_a.label = 13;
case 13:
_a.trys.push([13, 15, , 16]);
return [4 /*yield*/, callback(cleanRequest)];
case 14:
ourResponse = _a.sent();
return [3 /*break*/, 16];
case 15:
err_5 = _a.sent();
throw err_5;
case 16:
if (typeof ourResponse.body === 'object') {
ourResponse.body = JSON.stringify(ourResponse.body);
if (outResponse.json !== undefined) {
outResponse.headers = _.assign(outResponse.headers || {}, { 'Content-Type': 'application/json' });
outResponse.body = JSON.stringify(outResponse.json);
delete outResponse.json;
}
defaultResponse = __assign({ status: 200, body: '', headers: {} }, ourResponse);
defaultResponse = __assign({ status: 200 }, outResponse);
response.writeHead(defaultResponse.status, defaultResponse.headers);

@@ -177,3 +138,3 @@ response.end(defaultResponse.body || "");

});
}, { explain: function () { return "respond with callback " + callback.toString(); } });
}, { explain: function () { return 'respond using provided callback' + (callback.name ? " (" + callback.name + ")" : ''); } });
return responder;

@@ -180,0 +141,0 @@ },

import { Method } from "../types";
import { RequestMatcher } from "./mock-rule-types";
import { RequestMatcher, MockRuleCtx } from "./mock-rule-types";
export declare type MatcherData = (WildcardMatcherData | SimpleMatcherData | HeaderMatcherData | FormDataMatcherData);

@@ -17,4 +17,5 @@ export declare type MatcherType = MatcherData['type'];

path: string;
ctx: MockRuleCtx | undefined;
readonly type: 'simple';
constructor(method: Method, path: string);
constructor(method: Method, path: string, ctx?: MockRuleCtx | undefined);
}

@@ -21,0 +22,0 @@ export declare class HeaderMatcherData {

"use strict";
/**
* @module MockRuleData
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -50,5 +53,6 @@ return new (P || (P = Promise))(function (resolve, reject) {

var SimpleMatcherData = /** @class */ (function () {
function SimpleMatcherData(method, path) {
function SimpleMatcherData(method, path, ctx) {
this.method = method;
this.path = path;
this.ctx = ctx;
this.type = 'simple';

@@ -114,4 +118,12 @@ }

var url = normalize_url_1.default(data.path);
console.log("method: " + methodName);
console.log("url: " + url);
return _.assign(function (request) {
return request.method === methodName && normalize_url_1.default(request.url) === url;
var matchUrl = normalize_url_1.default(request.url);
console.log(data.ctx);
if (data.ctx && data.ctx.matchBy === 'path') {
matchUrl = normalize_url_1.default(request.path);
}
console.log("matchurl: " + matchUrl);
return request.method === methodName && matchUrl === url;
}, { explain: function () { return "making " + methodName + "s for " + data.path; } });

@@ -118,0 +130,0 @@ },

@@ -0,1 +1,4 @@

/**
* @module MockRule
*/
import { Explainable, OngoingRequest, CompletedRequest, Response } from "../types";

@@ -12,2 +15,5 @@ import { MatcherData } from "./matchers";

}
export interface MockRuleCtx {
matchBy?: string;
}
export interface MockRuleData {

@@ -21,6 +27,10 @@ matchers: MatcherData[];

}
export declare type RequestMatcher = ((request: OngoingRequest) => boolean | Promise<boolean>) & RuleExplainable;
export declare type RequestHandler = ((request: OngoingRequest, response: Response) => Promise<void>) & RuleExplainable;
export interface RequestMatcher extends RuleExplainable {
(request: OngoingRequest): boolean | Promise<boolean>;
}
export interface RequestHandler extends RuleExplainable {
(request: OngoingRequest, response: Response): Promise<void>;
}
export interface RuleCompletionChecker extends RuleExplainable {
(this: MockRule): boolean;
}
"use strict";
/**
* @module MockRule
*/
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=mock-rule-types.js.map
"use strict";
/**
* @module MockRule
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -39,3 +42,3 @@ return new (P || (P = Promise))(function (resolve, reject) {

var uuid = require("uuid/v4");
var _ = require("lodash");
var request_utils_1 = require("../util/request-utils");
var matching = require("./matchers");

@@ -69,5 +72,5 @@ var handling = require("./handlers");

var completedAndRecordedPromise = (function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
var buffer, result, _a, _b, _c, _d;
return __generator(this, function (_e) {
switch (_e.label) {
var buffer;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:

@@ -77,28 +80,4 @@ buffer = req.body.asBuffer();

case 1:
_e.sent();
_b = (_a = _(req).pick([
'protocol',
'method',
'url',
'hostname',
'path',
'headers'
])).assign;
_c = {};
_d = {};
return [4 /*yield*/, buffer];
case 2:
_d.buffer = _e.sent();
return [4 /*yield*/, req.body.asText().catch(function () { return undefined; })];
case 3:
_d.text = _e.sent();
return [4 /*yield*/, req.body.asJson().catch(function () { return undefined; })];
case 4:
_d.json = _e.sent();
return [4 /*yield*/, req.body.asFormData().catch(function () { return undefined; })];
case 5:
result = _b.apply(_a, [(_c.body = (_d.formData = _e.sent(),
_d),
_c)]).valueOf();
return [2 /*return*/, result];
_a.sent();
return [2 /*return*/, request_utils_1.waitForCompletedRequest(req)];
}

@@ -105,0 +84,0 @@ });

@@ -0,1 +1,4 @@

/**
* @module Mockttp
*/
import { CompletedRequest, MockedEndpoint as MockedEndpointInterface } from '../types';

@@ -2,0 +5,0 @@ import { MockRule } from '../rules/mock-rule';

"use strict";
/**
* @module Mockttp
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +6,0 @@ var MockedEndpoint = /** @class */ (function () {

@@ -5,2 +5,8 @@ import { OngoingRequest } from "../types";

import { MockedEndpoint } from "./mocked-endpoint";
/**
* A in-process Mockttp implementation. This starts servers on the local machine in the
* current process, and exposes methods to directly manage them.
*
* This class does not work in browsers, as it expects to be able to start HTTP servers.
*/
export default class MockttpServer extends AbstractMockttp implements Mockttp {

@@ -25,2 +31,4 @@ private rules;

private isComplete;
private explainRequest(request);
private suggestRule(request);
}
"use strict";
/**
* @module Mockttp
*/
var __extends = (this && this.__extends) || (function () {

@@ -63,3 +66,8 @@ var extendStatics = Object.setPrototypeOf ||

var promise_1 = require("../util/promise");
// Provides all the external API, uses that to build and manage the rules list, and interrogate our recorded requests
/**
* A in-process Mockttp implementation. This starts servers on the local machine in the
* current process, and exposes methods to directly manage them.
*
* This class does not work in browsers, as it expects to be able to start HTTP servers.
*/
var MockttpServer = /** @class */ (function (_super) {

@@ -196,5 +204,5 @@ __extends(MockttpServer, _super);

return [3 /*break*/, 5];
case 4: return [2 /*return*/, new Promise(function (resolve, reject) {
_this.server = destroyable_server_1.default(_this.app.listen(port, resolve));
})];
case 4:
this.server = destroyable_server_1.default(this.app.listen(port));
_b.label = 5;
case 5: return [2 /*return*/, new Promise(function (resolve, reject) {

@@ -228,5 +236,8 @@ _this.server.on('listening', resolve);

console.log("Stopping server at " + this.url);
if (!this.server) return [3 /*break*/, 2];
return [4 /*yield*/, this.server.destroy()];
case 1:
_a.sent();
_a.label = 2;
case 2:
this.reset();

@@ -307,3 +318,3 @@ return [2 /*return*/];

return [3 /*break*/, 7];
case 4: return [4 /*yield*/, explainRequest(request)];
case 4: return [4 /*yield*/, this.explainRequest(request)];
case 5:

@@ -325,3 +336,3 @@ requestExplanation = _c.sent();

_b = (_a = response).end;
return [4 /*yield*/, suggestRule(request)];
return [4 /*yield*/, this.suggestRule(request)];
case 6:

@@ -356,46 +367,46 @@ _b.apply(_a, [_c.sent()]);

};
MockttpServer.prototype.explainRequest = function (request) {
return __awaiter(this, void 0, void 0, function () {
var msg, bodyText;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
msg = request.method + " request to " + request.url;
return [4 /*yield*/, request.body.asText()];
case 1:
bodyText = _a.sent();
if (bodyText)
msg += " with body `" + bodyText + "`";
if (!_.isEmpty(request.headers)) {
msg += " with headers:\n" + JSON.stringify(request.headers, null, 2);
}
return [2 /*return*/, msg];
}
});
});
};
MockttpServer.prototype.suggestRule = function (request) {
return __awaiter(this, void 0, void 0, function () {
var msg, isFormRequest, formBody;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
msg = "You can fix this by adding a rule to match this request, for example:\n";
msg += "mockServer." + request.method.toLowerCase() + "(\"" + request.path + "\")";
isFormRequest = !!request.headers["content-type"] && request.headers["content-type"].indexOf("application/x-www-form-urlencoded") > -1;
return [4 /*yield*/, request.body.asFormData().catch(function () { return undefined; })];
case 1:
formBody = _a.sent();
if (isFormRequest && !!formBody) {
msg += ".withForm(" + JSON.stringify(formBody) + ")";
}
msg += '.thenReply(200, "your response");';
return [2 /*return*/, msg];
}
});
});
};
return MockttpServer;
}(mockttp_1.AbstractMockttp));
exports.default = MockttpServer;
function explainRequest(request) {
return __awaiter(this, void 0, void 0, function () {
var msg, bodyText;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
msg = request.method + " request to " + request.url;
return [4 /*yield*/, request.body.asText()];
case 1:
bodyText = _a.sent();
if (bodyText)
msg += " with body `" + bodyText + "`";
if (!_.isEmpty(request.headers)) {
msg += " with headers:\n" + JSON.stringify(request.headers, null, 2);
}
return [2 /*return*/, msg];
}
});
});
}
function suggestRule(request) {
return __awaiter(this, void 0, void 0, function () {
var msg, isFormRequest, formBody;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
msg = "You can fix this by adding a rule to match this request, for example:\n";
msg += "mockServer." + request.method.toLowerCase() + "(\"" + request.path + "\")";
isFormRequest = !!request.headers["content-type"] && request.headers["content-type"].indexOf("application/x-www-form-urlencoded") > -1;
return [4 /*yield*/, request.body.asFormData().catch(function () { return undefined; })];
case 1:
formBody = _a.sent();
if (isFormRequest && !!formBody) {
msg += ".withForm(" + JSON.stringify(formBody) + ")";
}
msg += '.thenReply(200, "your response");';
return [2 /*return*/, msg];
}
});
});
}
//# sourceMappingURL=mockttp-server.js.map
"use strict";
/**
* @module Internal
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +6,0 @@ var stream = require("stream");

"use strict";
/**
* @module Mockttp
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -3,0 +6,0 @@ return new (P || (P = Promise))(function (resolve, reject) {

"use strict";
/**
* @module Internal
*/
var __assign = (this && this.__assign) || Object.assign || function(t) {

@@ -3,0 +6,0 @@ for (var s, i = 1, n = arguments.length; i < n; i++) {

/// <reference types="node" />
/**
* @module Internal
*/
import stream = require('stream');

@@ -47,4 +50,16 @@ import express = require("express");

}
/**
* A mocked endpoint provides methods to see the current state of
* a mock rule.
*/
export interface MockedEndpoint {
id: string;
/**
* Get the requests that this endpoint has seen so far.
*
* This method returns a promise, which resolves with the requests seen
* up until now. The returned lists are immutable, so won't change if more
* requests rrive in future. Call `getSeenRequests` again later to get
* an updated list.
*/
getSeenRequests(): Promise<CompletedRequest[]>;

@@ -51,0 +66,0 @@ }

"use strict";
/**
* @module Internal
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +6,0 @@ exports.DEFAULT_STANDALONE_PORT = 45456;

/// <reference types="node" />
/**
* @module Internal
*/
import net = require("net");

@@ -3,0 +6,0 @@ export interface DestroyableServer extends net.Server {

"use strict";
/**
* @module Internal
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +6,0 @@ // Mostly from https://github.com/isaacs/server-destroy (which seems to be unmaintained)

"use strict";
/**
* @module Internal
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +6,0 @@ var fs = require("fs");

"use strict";
/**
* @module Internal
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +6,0 @@ var normalizeUrl = require("normalize-url");

@@ -0,1 +1,4 @@

/**
* @module Internal
*/
export declare function filter<T>(array: T[], test: (t: T) => Promise<boolean> | boolean): Promise<T[]>;
"use strict";
/**
* @module Internal
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -3,0 +6,0 @@ return new (P || (P = Promise))(function (resolve, reject) {

@@ -22,3 +22,2 @@ /// <reference types="node" />

private caKey;
private certKeys;
private certCache;

@@ -25,0 +24,0 @@ constructor(caKey: PEM, caCert: PEM);

"use strict";
/**
* @module Internal
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -3,0 +6,0 @@ return new (P || (P = Promise))(function (resolve, reject) {

{
"name": "@kronoslive/mockttp",
"version": "0.5.2",
"version": "0.7.2",
"description": "Mock HTTP server for testing HTTP clients and stubbing webservices",

@@ -11,10 +11,15 @@ "main": "dist/main.js",

},
"runkitExampleFilename": "./docs/runkitExample.js",
"files": [
"dist",
"src",
"custom-typings"
"custom-typings",
"docs",
"typedoc"
],
"scripts": {
"prebuild": "rimraf dist/*",
"build": "tsc && cp src/standalone/schema.gql dist/standalone/schema.gql",
"build": "npm run build:src && npm run build:doc",
"build:src": "tsc && cp src/standalone/schema.gql dist/standalone/schema.gql",
"build:doc": "typedoc --excludeExternals --readme none --exclude '**/+(main-browser|standalone-bin).ts' --out typedoc/ src/ custom-typings/",
"test": "npm run build && npm run test:mocha && npm run test:browser",

@@ -25,2 +30,3 @@ "test:mocha": "NODE_EXTRA_CA_CERTS=./test/fixtures/test-ca.pem mocha -r ts-node/register 'test/**/*.spec.ts'",

"standalone": "npm run build && node -e 'require(\".\").getStandalone({ debug: true }).start()'",
"ci": "npm run test && catch-uncommitted",
"graphiql": "npm run build && node ./standalone-graphiql.js",

@@ -62,2 +68,3 @@ "prepack": "npm run build"

"@types/sinon-chai": "^2.7.27",
"@types/source-map-support": "^0.4.0",
"@types/url-search-params": "^0.10.0",

@@ -67,2 +74,3 @@ "@types/uuid": "^3.4.0",

"apollo-link": "^1.0.5",
"catch-uncommitted": "^1.0.0",
"chai": "^3.5.0",

@@ -85,6 +93,9 @@ "chai-as-promised": "^6.0.0",

"sinon-chai": "^2.8.0",
"source-map-support": "^0.5.3",
"tmp-promise": "^1.0.3",
"ts-loader": "^2.3.7",
"ts-node": "^3.3.0",
"typescript": "^2.6.2",
"typedoc": "^0.10.0",
"typedoc-plugin-external-module-name": "^1.1.1",
"typescript": "^2.7.1",
"url-search-params": "^0.10.0",

@@ -107,3 +118,3 @@ "webpack": "^3.7.1"

"graphql-subscriptions": "^0.5.5",
"graphql-tools": "^2.11.0",
"graphql-tools": "~2.18.0",
"lodash": "^4.16.4",

@@ -110,0 +121,0 @@ "node-forge": "^0.7.1",

@@ -1,36 +0,32 @@

# Mockttp [![Travis Build Status](https://img.shields.io/travis/pimterry/mockttp.svg)](https://travis-ci.org/pimterry/mockttp)
# Mockttp [![Travis Build Status](https://img.shields.io/travis/pimterry/mockttp.svg)](https://travis-ci.org/pimterry/mockttp) [![Try Mockttp on RunKit](https://badge.runkitcdn.com/mockttp.svg)](https://npm.runkit.com/mockttp)
**Mockttp is the HTTP integration tool you've been searching for these long years.**
Mockttp lets you quickly & reliably fake HTTP responses for testing, and assert
on the requests made by your code.
Write JS tests that _truly_ integration test your HTTP. Quickly build a fake server, or
transparently proxy requests your code sends to other domains. Write mocks that work
universally in node and the browser. Get strong types built-in & promises throughout,
with a library designed for modern JS & async/await, and enjoy helpful debuggability
with self-explaining mocks.
There's a lot of tools to do this, but typically by stubbing the HTTP functions in your
process at the JS level. That ties you to a specific environment, doesn't test the
real requests that'd be made, and only works for requests made in the same JS processs.
It's inflexible, limiting and inaccurate, and often unreliable & tricky to debug too.
Mockttp lets you truly integration test your HTTP(S) requests with thorough
library-agnostic HTTP mocking that can mock requests from your code, your dependencies,
your subprocesses, native code, your server (from your browser), every crazy npm library
you've installed and even remote devices (if they use your machine as a proxy). See
the actual requests that would be made, and write tests that check what will really
hit the wire, and how your whole stack will handle the response, not just the function
calls you think you'll make.
Mockttp is here to make this better.
HTTP integration testing is a mess, and Mockttp is here to make it better.
Mockttp allows you to do accurate true integration testing, writing one set of tests that
works out of the box in node or browsers, with support for transparent proxying & HTTPS,
strong typing & promises throughout, fast & safe parallel testing, and helpful
built-in debuggability support all the way down.
_This is all still in early development, not quite complete or stable, and subject to change!_
## Features
## Er, what?
Let's get specific. Mockttp lets you:
Ok, let's summarize. Mockttp lets you:
* Write **easy, fast & reliable node.js & browser HTTP integration tests**
* Fake server responses and verify requests made by your code
* **Intercept, mock and proxy HTTPS too**, with built-in certificate generation
* **Mock HTTP requests from inside & outside your process/tab**, including subprocesses, native code, remote devices, and more
* Stub and mock requests transparently, as an **HTTP mocking proxy**, as well as serving traffic directly
* **Mock servers for node & browsers with the same code** (universal/'isomorphic' HTTP mocking)
* **Safely mock HTTP in parallel**, with autoconfiguration of ports, mock URLs and proxy settings
* **Stub server responses** and **verify HTTP requests** made by your code
* **Intercept HTTPS** too, with built-in self-signed certificate generation
* **Mock requests inside or outside your process/tab**, including subprocesses, native code, remote devices, and more
* **Test true real-world behaviour**, verifying the real requests made, and testing exactly how your whole stack will handle a response in reality
* Stub direct requests, or transparently stub requests elsewhere as an **HTTP mocking proxy**
* **Mock for node & browser tests with the same code** (universal/'isomorphic' HTTP mocking)
* **Safely mock HTTP in parallel**, with autoconfiguration of ports, mock URLs and proxy settings, for super-charged integration testing
* **Debug your tests easily**, with full explainability of all mock matches & misses, mock autosuggestions, and an extra detailed debug mode
* Write modern tests, with promises all the way down and **strong typing** (with TypeScript) throughout.
* Write modern test code, with promises all the way down, async/await, and **strong typing** (with TypeScript) throughout

@@ -45,45 +41,63 @@ ## Get Started

To run an HTTP integration test, you need to:
* Start a Mockttp server
* Mock the endpoints you're interested in
* Make some real HTTP requests
* Assert on the results
Here's a simple minimal example of all that using plain promises, Mocha, Chai & Superagent, which works out of the box in Node and modern browsers:
```typescript
const request = require("request");
const superagent = require("superagent");
const mockServer = require("mockttp").getLocal();
describe("Mockttp", () => {
// Start your server
beforeEach(() => mockServer.start(8080));
afterEach(() => mockServer.stop());
it("mocks requests", () => {
return mockServer.get("/mocked-endpoint").thenReply(200, "How delightful")
.then(() =>
request.get("http://localhost:8080/mocked-endpoint")
).then((response) =>
expect(response).to.equal("How delightful")
);
});
it("lets you mock requests, and assert on the results", () =>
// Mock your endpoints
mockServer.get("/mocked-path").thenReply(200, "A mocked response")
.then(() => {
// Make a request
return superagent.get("http://localhost:8080/mocked-path");
}).then(() => {
// Assert on the results
expect(response.text).to.equal("A mocked response");
});
);
});
```
it("works best with async/await", async () => {
await mockServer.get("/mocked-endpoint").thenReply(200, "Tip top testing")
(Want to play with this yourself? Try running a standalone version live on RunKit: https://npm.runkit.com/mockttp)
// Want to be agnostic to the mock port, to run tests in parallel?
// Try mockServer.url or .urlFor(path):
let response = await request.get(mockServer.urlFor("/mocked-endpoint"));
That is pretty easy, but we can make this simpler & more powerful. Let's take a look at some more fancy features:
expect(response).to.equal("Tip top testing");
});
```typescript
const superagent = require("superagent");
const mockServer = require("mockttp").getLocal();
it("can proxy requests made to any other hosts", async () => {
await mockServer.get("http://google.com").thenReply(200, "I can't believe it's not google!");
describe("Mockttp", () => {
// Note that there's no start port here, so we dynamically find a free one instead
beforeEach(() => mockServer.start());
afterEach(() => mockServer.stop());
// One of the _many_ ways to enable an HTTP proxy:
let proxiedRequest = request.defaults({ proxy: mockServer.url });
it("lets you mock without specifying a port, allowing parallel testing", async () => {
// Simplify promises with async/await in supported environments (Chrome 55+/Node 8+/Babel/TypeScript)
await mockServer.get("/mocked-endpoint").thenReply(200, "Tip top testing")
let response = await proxiedRequest.get("http://google.com");
// Try mockServer.url or .urlFor(path) to get a the dynamic URL for the server's port
let response = await superagent.get(mockServer.urlFor("/mocked-endpoint"));
expect(response).to.equal("I can't believe it's not google!");
expect(response.text).to.equal("Tip top testing");
});
it("also allows request verification", async () => {
it("lets you verify the request details the mockttp server receives", async () => {
const endpointMock = await mockServer.get("/mocked-endpoint").thenReply(200, "hmm?");
await request.get(mockServer.urlFor("/mocked-endpoint"));
await superagent.get(mockServer.urlFor("/mocked-endpoint"));
// Inspect the mock to get the requests it received and assert on their details
const requests = await endpointMock.getSeenRequests();

@@ -93,7 +107,25 @@ expect(requests.length).to.equal(1);

});
it("lets you proxy requests made to any other hosts", async () => {
// Match a full URL instead of just a path to mock proxied requests
await mockServer.get("http://google.com").thenReply(200, "I can't believe it's not google!");
// One of the many ways to use a proxy - this assumes Node & superagent-proxy.
// In a browser, you can simply use the browser settings instead.
let response = await superagent.get("http://google.com").proxy(server.url);
expect(response).to.equal("I can't believe it's not google!");
});
});
```
These examples uses Mocha, Chai and Superagent, but none of those are required: Mockttp will work with any testing tools that can handle promises (and with minor tweaks, many that can't), and can mock requests from any library, tool or device you might care to use.
## Documentation
* [In-depth setup guide](docs/setup.md)
* [API reference](https://pimterry.github.io/mockttp/modules/mockttp.html)
## Credits
* Many thanks to https://github.com/vieiralucas for donating the package name!
* Many thanks to https://github.com/vieiralucas for donating the package name!

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import { MockedEndpointData, MockedEndpoint, CompletedRequest } from "../types";

@@ -2,0 +6,0 @@

@@ -0,1 +1,5 @@

/**
* @module Mockttp
*/
import TypedError = require('typed-error');

@@ -6,4 +10,10 @@ import getFetch = require('fetch-ponyfill');

import { SubscriptionClient } from 'subscriptions-transport-ws';
const { fetch, Headers } = getFetch();
const {
/** @hidden */
fetch,
/** @hidden */
Headers
} = getFetch();
import { ProxyConfig, Method, MockedEndpoint, OngoingRequest } from "../types";

@@ -14,3 +24,3 @@ import {

} from "../rules/mock-rule-types";
import PartialMockRule from "../rules/partial-mock-rule";
import MockRuleBuilder from "../rules/mock-rule-builder";
import { Mockttp, AbstractMockttp, MockttpOptions } from "../mockttp";

@@ -45,4 +55,6 @@ import { MockServerConfig } from "../standalone/mockttp-standalone";

/** @hidden */
interface RequestData { }
/** @hidden */
interface MockedEndpointState {

@@ -53,2 +65,8 @@ id: string;

/**
* A Mockttp implementation, controlling a remote Mockttp standalone server.
*
* This starts servers by making requests to the remote standalone server, and exposes
* methods to directly manage them.
*/
export default class MockttpClient extends AbstractMockttp implements Mockttp {

@@ -55,0 +73,0 @@ private readonly standaloneServerUrl = `http://localhost:${DEFAULT_STANDALONE_PORT}`;

@@ -0,1 +1,5 @@

/**
* @module Mockttp
*/
import MockttpServer from "./server/mockttp-server";

@@ -14,2 +18,14 @@ import MockttpClient from "./client/mockttp-client";

/**
* Get a Mockttp instance on the local machine.
*
* In most simple environments, you can call this method directly and immediately
* get a Mockttp instance and start mocking servers.
*
* In node, the mocked servers will run in process and require no further setup.
*
* In browsers this is an alias for getRemote. You'll need to start a standalone server
* outside your tests before calling this, which will create and manage your fake servers
* outside the browser.
*/
export function getLocal(options: MockttpOptions = {}): Mockttp {

@@ -19,2 +35,8 @@ return new MockttpServer(options);

/**
* Get a Mockttp instance, controlled through a Mockttp standalone server.
*
* This connects to a Mockttp standalone server, and uses that to start
* and stop mock servers.
*/
export function getRemote(options: MockttpOptions = {}): Mockttp {

@@ -24,4 +46,15 @@ return new MockttpClient(options);

/**
* Get a standalone server, which can be used remotely to create & manage mock servers.
*
* This function exists so you can set up these servers programmatically, but for most
* usage you can just run your tests via the `mockttp` binary, which will automatically
* start and stop a standalone server for you:
*
* ```
* mockttp -c <your test command>
* ```
*/
export function getStandalone(options: StandaloneServerOptions = {}): MockttpStandalone {
return new MockttpStandalone(options);
}

@@ -1,27 +0,123 @@

import PartialMockRule from "./rules/partial-mock-rule";
/**
* @module Mockttp
*/
import MockRuleBuilder from "./rules/mock-rule-builder";
import { ProxyConfig, MockedEndpoint, Method, OngoingRequest } from "./types";
import { MockRuleData } from "./rules/mock-rule-types";
import { MockRuleData, MockRuleCtx } from "./rules/mock-rule-types";
import { CAOptions } from './util/tls';
/**
* A mockttp instance allow you to start and stop mock servers and control their behaviour.
*
* Call `.start()` to set up a server on a random port, use methods like `.get(url)`,
* `.post(url)` and `.anyRequest()` to get a {@link MockRuleBuilder} and start defining
* mock rules. Call `.stop()` when your test is complete.
*/
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.
*/
start(port?: number): Promise<void>;
/**
* Stop the mock server and reset the rules.
*/
stop(): Promise<void>;
/**
* Enable extra debug output so you can understand exactly what the server is doing.
*/
enableDebug(): void;
/**
* Reset the stored rules. Most of the time it's better to start & stop the server instead,
* but this can be useful in some special cases.
*/
reset(): void;
/**
* The root URL of the server.
*
* This will throw an error if read before the server is started.
*/
url: string;
/**
* The URL for a given path on the server.
*
* This will throw an error if read before the server is started.
*/
urlFor(path: string): string;
/**
* The port the server is running on.
*
* This will throw an error if read before the server is started.
*/
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)
* ```
*/
proxyEnv: ProxyConfig;
urlFor(path: string): string;
/**
* Get a builder for a mock rule that will match any requests on any path.
*/
anyRequest(): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match GET requests for the given path.
*/
get(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match POST requests for the given path.
*/
post(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match PUT requests for the given path.
*/
put(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match DELETE requests for the given path.
*/
delete(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match PATCH requests for the given path.
*/
patch(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
/**
* Get a builder for a mock rule that will match OPTIONS requests for the given path.
*
* 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.
*/
options(url: string, ctx?: MockRuleCtx): MockRuleBuilder;
anyRequest(): PartialMockRule;
get(url: string): PartialMockRule;
post(url: string): PartialMockRule;
put(url: string): PartialMockRule;
delete(url: string): PartialMockRule;
patch(url: string): PartialMockRule;
options(url: string): PartialMockRule;
/**
* Subscribe to hear about request details as they're 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.
*/
on(event: 'request', callback: (req: OngoingRequest) => void): Promise<void>;

@@ -36,2 +132,5 @@ }

/**
* @hidden
*/
export abstract class AbstractMockttp {

@@ -61,27 +160,27 @@ protected cors: boolean;

anyRequest(): PartialMockRule {
return new PartialMockRule(this.addRule);
anyRequest(): MockRuleBuilder {
return new MockRuleBuilder(this.addRule);
}
get(url: string): PartialMockRule {
return new PartialMockRule(Method.GET, url, this.addRule);
get(url: string, ctx?: MockRuleCtx): MockRuleBuilder {
return new MockRuleBuilder(Method.GET, url, this.addRule, ctx);
}
post(url: string): PartialMockRule {
return new PartialMockRule(Method.POST, url, this.addRule);
post(url: string, ctx?: MockRuleCtx): MockRuleBuilder {
return new MockRuleBuilder(Method.POST, url, this.addRule, ctx);
}
put(url: string): PartialMockRule {
return new PartialMockRule(Method.PUT, url, this.addRule);
put(url: string, ctx?: MockRuleCtx): MockRuleBuilder {
return new MockRuleBuilder(Method.PUT, url, this.addRule, ctx);
}
delete(url: string): PartialMockRule {
return new PartialMockRule(Method.DELETE, url, this.addRule);
delete(url: string, ctx?: MockRuleCtx): MockRuleBuilder {
return new MockRuleBuilder(Method.DELETE, url, this.addRule, ctx);
}
patch(url: string): PartialMockRule {
return new PartialMockRule(Method.PATCH, url, this.addRule);
patch(url: string, ctx?: MockRuleCtx): MockRuleBuilder {
return new MockRuleBuilder(Method.PATCH, url, this.addRule, ctx);
}
options(url: string): PartialMockRule {
options(url: string, ctx?: MockRuleCtx): MockRuleBuilder {
if (this.cors) {

@@ -94,5 +193,5 @@ throw new Error(`Cannot mock OPTIONS requests with CORS enabled.

}
return new PartialMockRule(Method.OPTIONS, url, this.addRule);
return new MockRuleBuilder(Method.OPTIONS, url, this.addRule, ctx);
}
}

@@ -0,1 +1,5 @@

/**
* @module MockRuleData
*/
import { RuleCompletionChecker, MockRule, RuleExplainable } from './mock-rule-types';

@@ -2,0 +6,0 @@

@@ -0,1 +1,5 @@

/**
* @module MockRuleData
*/
import _ = require('lodash');

@@ -6,5 +10,6 @@ import url = require('url');

import express = require("express");
import { OngoingRequest } from "../types";
import { waitForCompletedRequest } from '../util/request-utils';
import { CompletedRequest, OngoingRequest } from "../types";
import { RequestHandler } from "./mock-rule-types";
import { OutgoingHttpHeaders } from "http";

@@ -31,6 +36,15 @@ export type HandlerData = (

public data?: string,
public headers?: OutgoingHttpHeaders
public headers?: http.OutgoingHttpHeaders
) {}
}
export interface CallbackHandlerResult {
status?: number;
json?: any;
body?: string;
headers?: {
[key: string]: string;
};
}
export class CallbackHandlerData {

@@ -40,3 +54,3 @@ readonly type: 'callback' = 'callback';

constructor(
public callback: Function
public callback: (request: CompletedRequest) => CallbackHandlerResult
) {}

@@ -66,55 +80,31 @@ }

response.end(data || "");
}, { explain: () => `respond with status ${status}` + (data ? ` and body "${data}"` : "") });
}, { explain: () => `respond with status ${status}` + (headers ? `, headers ${JSON.stringify(headers)}` : "") + (data ? ` and body "${data}"` : "") });
return responder;
},
callback: ({callback}: CallbackHandlerData): RequestHandler => {
callback: ({ callback }: CallbackHandlerData): RequestHandler => {
let responder = _.assign(async function(request: OngoingRequest, response: express.Response) {
let buffer, text, json, formData;
let req = await waitForCompletedRequest(request);
let outResponse: CallbackHandlerResult;
try {
buffer = await request.body.asBuffer();
} catch (err) {
buffer = undefined;
outResponse = await callback(req);
} catch (error) {
response.writeHead(500, 'Callback handler threw an exception');
response.end(error.toString());
return;
}
try {
text = await request.body.asText();
} catch (err) {
text = undefined;
if (outResponse.json !== undefined) {
outResponse.headers = _.assign(outResponse.headers || {}, { 'Content-Type': 'application/json' });
outResponse.body = JSON.stringify(outResponse.json);
delete outResponse.json;
}
try {
json = await request.body.asJson();
} catch (err) {
json = undefined;
}
try {
formData = await request.body.asFormData();
} catch (err) {
formData = undefined;
}
const cleanRequest = {
protocol: request.protocol,
method: request.method,
url: request.url,
hostname: request.hostname,
path: request.path,
headers: request.headers,
body: { buffer, text, json, formData }
}
let ourResponse
try {
ourResponse = await callback(cleanRequest);
} catch (err) {
throw err;
}
if (typeof ourResponse.body === 'object') {
ourResponse.body = JSON.stringify(ourResponse.body);
}
const defaultResponse = {
status: 200,
body: '',
headers: {},
...ourResponse
...outResponse
};
response.writeHead(defaultResponse.status, defaultResponse.headers);
response.end(defaultResponse.body || "");
}, { explain: () => `respond with callback ${callback.toString()}` });
}, { explain: () => 'respond using provided callback' + (callback.name ? ` (${callback.name})` : '') });
return responder;

@@ -126,3 +116,3 @@ },

const { protocol, hostname, port, path } = url.parse(originalUrl);
if (!hostname) {

@@ -133,3 +123,3 @@ throw new Error(

}
let makeRequest = protocol === 'https:' ? https.request : http.request;

@@ -157,3 +147,3 @@

clientRes.status(serverRes.statusCode!);
serverRes.pipe(clientRes);

@@ -163,3 +153,3 @@ serverRes.on('end', resolve);

});
clientReq.body.rawStream.pipe(serverReq);

@@ -166,0 +156,0 @@

@@ -0,5 +1,9 @@

/**
* @module MockRuleData
*/
import * as _ from "lodash";
import { OngoingRequest, Method } from "../types";
import { RequestMatcher } from "./mock-rule-types";
import { RequestMatcher, MockRuleCtx } from "./mock-rule-types";
import { MockRule } from "./mock-rule";

@@ -33,3 +37,4 @@ import normalizeUrl from "../util/normalize-url";

public method: Method,
public path: string
public path: string,
public ctx?: MockRuleCtx
) {}

@@ -90,5 +95,14 @@ }

return _.assign((request: OngoingRequest) =>
request.method === methodName && normalizeUrl(request.url) === url
, { explain: () => `making ${methodName}s for ${data.path}` });
console.log(`method: ${methodName}`);
console.log(`url: ${url}`);
return _.assign((request: OngoingRequest) => {
let matchUrl = normalizeUrl(request.url);
console.log(data.ctx);
if (data.ctx && data.ctx.matchBy === 'path') {
matchUrl = normalizeUrl(request.path)
}
console.log(`matchurl: ${matchUrl}`);
return request.method === methodName && matchUrl === url
}, { explain: () => `making ${methodName}s for ${data.path}` });
},

@@ -95,0 +109,0 @@

@@ -0,1 +1,5 @@

/**
* @module MockRule
*/
import { Explainable, OngoingRequest, CompletedRequest, Response, Method } from "../types";

@@ -16,2 +20,6 @@ import { MatcherData } from "./matchers";

export interface MockRuleCtx {
matchBy?: string
}
export interface MockRuleData {

@@ -27,7 +35,12 @@ matchers: MatcherData[];

export type RequestMatcher = ((request: OngoingRequest) => boolean | Promise<boolean>) & RuleExplainable;
export type RequestHandler = ((request: OngoingRequest, response: Response) => Promise<void>) & RuleExplainable;
export interface RequestMatcher extends RuleExplainable {
(request: OngoingRequest): boolean | Promise<boolean>;
}
export interface RequestHandler extends RuleExplainable {
(request: OngoingRequest, response: Response): Promise<void>
}
export interface RuleCompletionChecker extends RuleExplainable {
(this: MockRule): boolean;
}

@@ -0,4 +1,9 @@

/**
* @module MockRule
*/
import uuid = require("uuid/v4");
import * as _ from "lodash";
import { waitForCompletedRequest } from '../util/request-utils';
import { OngoingRequest, CompletedRequest, Response } from "../types";

@@ -56,19 +61,3 @@ import {

let result = _(req).pick([
'protocol',
'method',
'url',
'hostname',
'path',
'headers'
]).assign({
body: {
buffer: await buffer,
text: await req.body.asText().catch(() => undefined),
json: await req.body.asJson().catch(() => undefined),
formData: await req.body.asFormData().catch(() => undefined)
}
}).valueOf();
return result;
return waitForCompletedRequest(req);
})();

@@ -75,0 +64,0 @@

@@ -0,1 +1,5 @@

/**
* @module Mockttp
*/
import { CompletedRequest, MockedEndpoint as MockedEndpointInterface } from '../types';

@@ -2,0 +6,0 @@ import { MockRule } from '../rules/mock-rule';

@@ -0,1 +1,5 @@

/**
* @module Mockttp
*/
import net = require("net");

@@ -14,3 +18,3 @@ import http = require("http");

import { MockRuleData } from "../rules/mock-rule-types";
import PartialMockRule from "../rules/partial-mock-rule";
import MockRuleBuilder from "../rules/mock-rule-builder";
import { CAOptions, getCA } from '../util/tls';

@@ -24,3 +28,9 @@ import destroyable, { DestroyableServer } from "../util/destroyable-server";

// Provides all the external API, uses that to build and manage the rules list, and interrogate our recorded requests
/**
* A in-process Mockttp implementation. This starts servers on the local machine in the
* current process, and exposes methods to directly manage them.
*
* This class does not work in browsers, as it expects to be able to start HTTP servers.
*/
export default class MockttpServer extends AbstractMockttp implements Mockttp {

@@ -32,3 +42,3 @@ private rules: MockRule[] = [];

private app: express.Application;
private server: DestroyableServer;
private server: DestroyableServer | undefined;

@@ -135,10 +145,8 @@ private eventEmitter: EventEmitter;

} else {
return new Promise<void>((resolve, reject) => {
this.server = destroyable(this.app.listen(port, resolve));
});
this.server = destroyable(this.app.listen(port));
}
return new Promise<void>((resolve, reject) => {
this.server.on('listening', resolve);
this.server.on('error', (e: any) => {
this.server!.on('listening', resolve);
this.server!.on('error', (e: any) => {
// Although we try to pick a free port, we may have race conditions, if something else

@@ -150,3 +158,3 @@ // takes the same port at the same time. If you haven't explicitly picked a port, and

this.server.close(); // Don't bother waiting for this, it can stop on its own time
this.server!.close(); // Don't bother waiting for this, it can stop on its own time
resolve(this.start());

@@ -163,3 +171,4 @@ } else {

await this.server.destroy();
if (this.server) await this.server.destroy();
this.reset();

@@ -225,3 +234,3 @@ }

} else {
let requestExplanation = await explainRequest(request);
let requestExplanation = await this.explainRequest(request);
if (this.debug) console.warn(`Unmatched request received: ${requestExplanation}`);

@@ -242,3 +251,3 @@

response.end(await suggestRule(request));
response.end(await this.suggestRule(request));
}

@@ -270,32 +279,32 @@ } catch (e) {

}
}
async function explainRequest(request: OngoingRequest): Promise<string> {
let msg = `${request.method} request to ${request.url}`;
let bodyText = await request.body.asText();
if (bodyText) msg += ` with body \`${bodyText}\``;
if (!_.isEmpty(request.headers)) {
msg += ` with headers:\n${JSON.stringify(request.headers, null, 2)}`;
private async explainRequest(request: OngoingRequest): Promise<string> {
let msg = `${request.method} request to ${request.url}`;
let bodyText = await request.body.asText();
if (bodyText) msg += ` with body \`${bodyText}\``;
if (!_.isEmpty(request.headers)) {
msg += ` with headers:\n${JSON.stringify(request.headers, null, 2)}`;
}
return msg;
}
return msg;
}
async function suggestRule(request: OngoingRequest): Promise<string> {
let msg = "You can fix this by adding a rule to match this request, for example:\n"
msg += `mockServer.${request.method.toLowerCase()}("${request.path}")`;
let isFormRequest = !!request.headers["content-type"] && request.headers["content-type"].indexOf("application/x-www-form-urlencoded") > -1;
let formBody = await request.body.asFormData().catch(() => undefined);
if (isFormRequest && !!formBody) {
msg += `.withForm(${JSON.stringify(formBody)})`;
private async suggestRule(request: OngoingRequest): Promise<string> {
let msg = "You can fix this by adding a rule to match this request, for example:\n"
msg += `mockServer.${request.method.toLowerCase()}("${request.path}")`;
let isFormRequest = !!request.headers["content-type"] && request.headers["content-type"].indexOf("application/x-www-form-urlencoded") > -1;
let formBody = await request.body.asFormData().catch(() => undefined);
if (isFormRequest && !!formBody) {
msg += `.withForm(${JSON.stringify(formBody)})`;
}
msg += '.thenReply(200, "your response");';
return msg;
}
msg += '.thenReply(200, "your response");';
return msg;
}

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import * as stream from 'stream';

@@ -2,0 +6,0 @@ import * as querystring from 'querystring';

@@ -0,1 +1,5 @@

/**
* @module Mockttp
*/
import * as path from 'path';

@@ -2,0 +6,0 @@ import * as fs from '../util/fs';

#!/usr/bin/env node
/**
* @module Internal
*/
import _ = require('lodash');

@@ -3,0 +7,0 @@ import childProcess = require('child_process');

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import * as _ from "lodash";

@@ -2,0 +6,0 @@

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import stream = require('stream');

@@ -49,5 +53,16 @@ import express = require("express");

// The external interface of a rule, for users to later verify with
/**
* A mocked endpoint provides methods to see the current state of
* a mock rule.
*/
export interface MockedEndpoint {
id: string;
/**
* Get the requests that this endpoint has seen so far.
*
* This method returns a promise, which resolves with the requests seen
* up until now. The returned lists are immutable, so won't change if more
* requests rrive in future. Call `getSeenRequests` again later to get
* an updated list.
*/
getSeenRequests(): Promise<CompletedRequest[]>;

@@ -54,0 +69,0 @@ }

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import net = require("net");

@@ -2,0 +6,0 @@ import http = require("http");

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import fs = require('fs');

@@ -2,0 +6,0 @@

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import * as normalizeUrl from "normalize-url";

@@ -2,0 +6,0 @@

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
export async function filter<T>(

@@ -2,0 +6,0 @@ array: T[],

@@ -0,1 +1,5 @@

/**
* @module Internal
*/
import * as uuid from 'uuid/v4';

@@ -56,3 +60,2 @@ import { pki, md } from 'node-forge';

private certKeys: { publicKey: {}, privateKey: {} };
private certCache: { [domain: string]: GeneratedCertificate };

@@ -59,0 +62,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 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

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc