@mbtest/mountebank 2.9.2-beta.9050
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/bin/mb +136 -0
- package/package.json +71 -0
- package/releases.json +52 -0
- package/src/cli/api.js +112 -0
- package/src/cli/cli.js +420 -0
- package/src/controllers/configController.js +64 -0
- package/src/controllers/feedController.js +115 -0
- package/src/controllers/homeController.js +58 -0
- package/src/controllers/imposterController.js +328 -0
- package/src/controllers/impostersController.js +215 -0
- package/src/controllers/logsController.js +52 -0
- package/src/models/behaviors.js +553 -0
- package/src/models/behaviorsValidator.js +186 -0
- package/src/models/compatibility.js +133 -0
- package/src/models/dryRunValidator.js +261 -0
- package/src/models/filesystemBackedImpostersRepository.js +908 -0
- package/src/models/http/baseHttpServer.js +207 -0
- package/src/models/http/headersMap.js +87 -0
- package/src/models/http/httpProxy.js +230 -0
- package/src/models/http/httpRequest.js +82 -0
- package/src/models/http/httpServer.js +18 -0
- package/src/models/http/index.js +18 -0
- package/src/models/https/cert/mb-cert.pem +20 -0
- package/src/models/https/cert/mb-csr.pem +16 -0
- package/src/models/https/cert/mb-key.pem +27 -0
- package/src/models/https/httpsServer.js +42 -0
- package/src/models/https/index.js +18 -0
- package/src/models/imposter.js +243 -0
- package/src/models/imposterPrinter.js +120 -0
- package/src/models/impostersRepository.js +49 -0
- package/src/models/inMemoryImpostersRepository.js +418 -0
- package/src/models/jsonpath.js +44 -0
- package/src/models/mbConnection.js +107 -0
- package/src/models/predicates.js +438 -0
- package/src/models/protocols.js +242 -0
- package/src/models/responseResolver.js +398 -0
- package/src/models/smtp/index.js +16 -0
- package/src/models/smtp/smtpRequest.js +60 -0
- package/src/models/smtp/smtpServer.js +109 -0
- package/src/models/tcp/index.js +18 -0
- package/src/models/tcp/tcpProxy.js +110 -0
- package/src/models/tcp/tcpRequest.js +23 -0
- package/src/models/tcp/tcpServer.js +156 -0
- package/src/models/tcp/tcpValidator.js +19 -0
- package/src/models/xpath.js +95 -0
- package/src/mountebank.js +245 -0
- package/src/public/images/arrow_down.png +0 -0
- package/src/public/images/arrow_up.png +0 -0
- package/src/public/images/book.jpg +0 -0
- package/src/public/images/dataflow.png +0 -0
- package/src/public/images/favicon.ico +0 -0
- package/src/public/images/forkme_right_orange_ff7600.png +0 -0
- package/src/public/images/mountebank.png +0 -0
- package/src/public/images/overview.gif +0 -0
- package/src/public/images/quote.png +0 -0
- package/src/public/images/tw-logo.png +0 -0
- package/src/public/scripts/jquery/jquery-3.6.1.min.js +2 -0
- package/src/public/scripts/urlHashHandler.js +31 -0
- package/src/public/stylesheets/application.css +424 -0
- package/src/public/stylesheets/ie.css +14 -0
- package/src/public/stylesheets/imposters.css +121 -0
- package/src/public/stylesheets/jqueryui/1.10.4/themes/smoothness/jquery-ui.css +1178 -0
- package/src/util/combinators.js +68 -0
- package/src/util/date.js +51 -0
- package/src/util/errors.js +55 -0
- package/src/util/helpers.js +131 -0
- package/src/util/inherit.js +28 -0
- package/src/util/ip.js +54 -0
- package/src/util/logger.js +83 -0
- package/src/util/middleware.js +256 -0
- package/src/util/scopedLogger.js +47 -0
- package/src/views/_footer.ejs +20 -0
- package/src/views/_header.ejs +113 -0
- package/src/views/_imposter.ejs +8 -0
- package/src/views/config.ejs +71 -0
- package/src/views/docs/api/behaviors/copy.ejs +427 -0
- package/src/views/docs/api/behaviors/decorate.ejs +182 -0
- package/src/views/docs/api/behaviors/lookup.ejs +220 -0
- package/src/views/docs/api/behaviors/shellTransform.ejs +153 -0
- package/src/views/docs/api/behaviors/wait.ejs +121 -0
- package/src/views/docs/api/behaviors.ejs +141 -0
- package/src/views/docs/api/contracts/addStub-description.ejs +10 -0
- package/src/views/docs/api/contracts/addStub.ejs +10 -0
- package/src/views/docs/api/contracts/config-description.ejs +32 -0
- package/src/views/docs/api/contracts/config.ejs +23 -0
- package/src/views/docs/api/contracts/home-description.ejs +18 -0
- package/src/views/docs/api/contracts/home.ejs +13 -0
- package/src/views/docs/api/contracts/imposter-description.ejs +439 -0
- package/src/views/docs/api/contracts/imposter.ejs +182 -0
- package/src/views/docs/api/contracts/imposters-description.ejs +13 -0
- package/src/views/docs/api/contracts/imposters.ejs +13 -0
- package/src/views/docs/api/contracts/logs-description.ejs +3 -0
- package/src/views/docs/api/contracts/logs.ejs +14 -0
- package/src/views/docs/api/contracts/stub-description.ejs +4 -0
- package/src/views/docs/api/contracts/stub.ejs +7 -0
- package/src/views/docs/api/contracts/stubs-description.ejs +4 -0
- package/src/views/docs/api/contracts/stubs.ejs +11 -0
- package/src/views/docs/api/contracts.ejs +133 -0
- package/src/views/docs/api/errors.ejs +64 -0
- package/src/views/docs/api/fault/connectionReset.ejs +31 -0
- package/src/views/docs/api/fault/randomDataThenClose.ejs +31 -0
- package/src/views/docs/api/faults.ejs +57 -0
- package/src/views/docs/api/injection.ejs +426 -0
- package/src/views/docs/api/json.ejs +205 -0
- package/src/views/docs/api/jsonpath.ejs +210 -0
- package/src/views/docs/api/mocks.ejs +130 -0
- package/src/views/docs/api/overview.ejs +968 -0
- package/src/views/docs/api/predicates/and.ejs +62 -0
- package/src/views/docs/api/predicates/contains.ejs +64 -0
- package/src/views/docs/api/predicates/deepEquals.ejs +114 -0
- package/src/views/docs/api/predicates/endsWith.ejs +66 -0
- package/src/views/docs/api/predicates/equals.ejs +125 -0
- package/src/views/docs/api/predicates/exists.ejs +118 -0
- package/src/views/docs/api/predicates/inject.ejs +67 -0
- package/src/views/docs/api/predicates/matches.ejs +66 -0
- package/src/views/docs/api/predicates/not.ejs +52 -0
- package/src/views/docs/api/predicates/or.ejs +79 -0
- package/src/views/docs/api/predicates/startsWith.ejs +62 -0
- package/src/views/docs/api/predicates.ejs +382 -0
- package/src/views/docs/api/proxies.ejs +191 -0
- package/src/views/docs/api/proxy/addDecorateBehavior.ejs +115 -0
- package/src/views/docs/api/proxy/addWaitBehavior.ejs +96 -0
- package/src/views/docs/api/proxy/injectHeaders.ejs +91 -0
- package/src/views/docs/api/proxy/predicateGenerators.ejs +600 -0
- package/src/views/docs/api/proxy/proxyModes.ejs +495 -0
- package/src/views/docs/api/stubs.ejs +391 -0
- package/src/views/docs/api/xpath.ejs +281 -0
- package/src/views/docs/cli/configFiles.ejs +133 -0
- package/src/views/docs/cli/customFormatters.ejs +53 -0
- package/src/views/docs/cli/help.ejs +6 -0
- package/src/views/docs/cli/replay.ejs +42 -0
- package/src/views/docs/cli/restart.ejs +10 -0
- package/src/views/docs/cli/save.ejs +68 -0
- package/src/views/docs/cli/start.ejs +234 -0
- package/src/views/docs/cli/stop.ejs +32 -0
- package/src/views/docs/commandLine.ejs +93 -0
- package/src/views/docs/communityExtensions.ejs +233 -0
- package/src/views/docs/gettingStarted.ejs +146 -0
- package/src/views/docs/mentalModel.ejs +51 -0
- package/src/views/docs/protocols/custom.ejs +231 -0
- package/src/views/docs/protocols/http.ejs +238 -0
- package/src/views/docs/protocols/https.ejs +246 -0
- package/src/views/docs/protocols/smtp.ejs +142 -0
- package/src/views/docs/protocols/tcp.ejs +431 -0
- package/src/views/docs/security.ejs +38 -0
- package/src/views/faqs.ejs +65 -0
- package/src/views/feed.ejs +33 -0
- package/src/views/imposter.ejs +22 -0
- package/src/views/imposters.ejs +33 -0
- package/src/views/index.ejs +89 -0
- package/src/views/license.ejs +30 -0
- package/src/views/logs.ejs +77 -0
- package/src/views/releases/v1.1.0.ejs +55 -0
- package/src/views/releases/v1.1.36.ejs +84 -0
- package/src/views/releases/v1.1.72.ejs +92 -0
- package/src/views/releases/v1.10.0.ejs +108 -0
- package/src/views/releases/v1.11.0.ejs +109 -0
- package/src/views/releases/v1.12.0.ejs +96 -0
- package/src/views/releases/v1.13.0.ejs +118 -0
- package/src/views/releases/v1.14.0.ejs +107 -0
- package/src/views/releases/v1.14.1.ejs +94 -0
- package/src/views/releases/v1.15.0.ejs +113 -0
- package/src/views/releases/v1.16.0.ejs +104 -0
- package/src/views/releases/v1.2.0.ejs +78 -0
- package/src/views/releases/v1.2.103.ejs +86 -0
- package/src/views/releases/v1.2.122.ejs +86 -0
- package/src/views/releases/v1.2.30.ejs +84 -0
- package/src/views/releases/v1.2.45.ejs +84 -0
- package/src/views/releases/v1.2.56.ejs +79 -0
- package/src/views/releases/v1.3.0.ejs +86 -0
- package/src/views/releases/v1.3.1.ejs +100 -0
- package/src/views/releases/v1.4.0.ejs +96 -0
- package/src/views/releases/v1.4.1.ejs +103 -0
- package/src/views/releases/v1.4.2.ejs +100 -0
- package/src/views/releases/v1.4.3.ejs +113 -0
- package/src/views/releases/v1.5.0.ejs +104 -0
- package/src/views/releases/v1.5.1.ejs +91 -0
- package/src/views/releases/v1.6.0.ejs +109 -0
- package/src/views/releases/v1.7.0.ejs +113 -0
- package/src/views/releases/v1.7.1.ejs +90 -0
- package/src/views/releases/v1.7.2.ejs +96 -0
- package/src/views/releases/v1.8.0.ejs +121 -0
- package/src/views/releases/v1.9.0.ejs +111 -0
- package/src/views/releases/v2.0.0.ejs +159 -0
- package/src/views/releases/v2.1.0.ejs +121 -0
- package/src/views/releases/v2.1.1.ejs +106 -0
- package/src/views/releases/v2.1.2.ejs +84 -0
- package/src/views/releases/v2.2.0.ejs +115 -0
- package/src/views/releases/v2.2.1.ejs +102 -0
- package/src/views/releases/v2.3.0.ejs +121 -0
- package/src/views/releases/v2.3.1.ejs +100 -0
- package/src/views/releases/v2.3.2.ejs +102 -0
- package/src/views/releases/v2.3.3.ejs +97 -0
- package/src/views/releases/v2.4.0.ejs +114 -0
- package/src/views/releases/v2.5.0.ejs +51 -0
- package/src/views/releases/v2.6.0.ejs +35 -0
- package/src/views/releases/v2.7.0.ejs +32 -0
- package/src/views/releases/v2.8.0.ejs +36 -0
- package/src/views/releases/v2.8.1.ejs +7 -0
- package/src/views/releases/v2.8.2.ejs +26 -0
- package/src/views/releases/v2.9.0.ejs +32 -0
- package/src/views/releases/v2.9.1.ejs +10 -0
- package/src/views/releases.ejs +26 -0
- package/src/views/sitemap.ejs +36 -0
- package/src/views/support.ejs +14 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const exceptions = require('../util/errors.js'),
|
|
4
|
+
helpers = require('../util/helpers.js'),
|
|
5
|
+
compatibility = require('../models/compatibility.js'),
|
|
6
|
+
dryRunValidator = require('../models/dryRunValidator.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The controller that gets and deletes single imposters
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates the imposter controller
|
|
15
|
+
* @param {Object} protocols - the protocol implementations supported by mountebank
|
|
16
|
+
* @param {Object} imposters - The map of ports to imposters
|
|
17
|
+
* @param {Object} logger - The logger
|
|
18
|
+
* @param {Boolean} allowInjection - Whether injection is allowed or not
|
|
19
|
+
* @returns {{get, del}}
|
|
20
|
+
*/
|
|
21
|
+
function create (protocols, imposters, logger, allowInjection) {
|
|
22
|
+
function isFlagSet (query, key) {
|
|
23
|
+
if (!helpers.defined(query[key])) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return query[key].toLowerCase() === 'true';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The function responding to GET /imposters/:id
|
|
31
|
+
* @memberOf module:controllers/imposterController#
|
|
32
|
+
* @param {Object} request - the HTTP request
|
|
33
|
+
* @param {Object} response - the HTTP response
|
|
34
|
+
* @returns {Object} - the promise
|
|
35
|
+
*/
|
|
36
|
+
async function get (request, response) {
|
|
37
|
+
const options = {
|
|
38
|
+
replayable: isFlagSet(request.query, 'replayable'),
|
|
39
|
+
removeProxies: isFlagSet(request.query, 'removeProxies')
|
|
40
|
+
},
|
|
41
|
+
imposter = await imposters.get(request.params.id),
|
|
42
|
+
json = await imposter.toJSON(options);
|
|
43
|
+
|
|
44
|
+
response.format({
|
|
45
|
+
json: () => response.send(json),
|
|
46
|
+
html: () => response.render('imposter', { imposter: json })
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Corresponds to DELETE /imposters/:id/savedProxyResponses
|
|
52
|
+
* Removes all saved proxy responses
|
|
53
|
+
* @memberOf module:controllers/imposterController#
|
|
54
|
+
* @param {Object} request - the HTTP request
|
|
55
|
+
* @param {Object} response - the HTTP response
|
|
56
|
+
* @returns {Object} A promise for testing
|
|
57
|
+
*/
|
|
58
|
+
async function resetProxies (request, response) {
|
|
59
|
+
const options = { replayable: false, removeProxies: false },
|
|
60
|
+
imposter = await imposters.get(request.params.id);
|
|
61
|
+
|
|
62
|
+
await imposters.stubsFor(request.params.id).deleteSavedProxyResponses();
|
|
63
|
+
const json = await imposter.toJSON(options);
|
|
64
|
+
|
|
65
|
+
response.format({
|
|
66
|
+
json: () => response.send(json),
|
|
67
|
+
html: () => response.render('imposter', { imposter: json })
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Corresponds to DELETE /imposters/:id/savedRequests
|
|
73
|
+
* Removes all saved requests
|
|
74
|
+
* @memberOf module:controllers/imposterController#
|
|
75
|
+
* @param {Object} request - the HTTP request
|
|
76
|
+
* @param {Object} response - the HTTP response
|
|
77
|
+
* @returns {Object} A promise for testing
|
|
78
|
+
*/
|
|
79
|
+
async function resetRequests (request, response) {
|
|
80
|
+
const imposter = await imposters.get(request.params.id);
|
|
81
|
+
await imposter.resetRequests();
|
|
82
|
+
const json = await imposter.toJSON();
|
|
83
|
+
|
|
84
|
+
response.format({
|
|
85
|
+
json: () => response.send(json),
|
|
86
|
+
html: () => response.render('imposter', { imposter: json })
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The function responding to DELETE /imposters/:id
|
|
92
|
+
* @memberOf module:controllers/imposterController#
|
|
93
|
+
* @param {Object} request - the HTTP request
|
|
94
|
+
* @param {Object} response - the HTTP response
|
|
95
|
+
* @returns {Object} A promise for testing
|
|
96
|
+
*/
|
|
97
|
+
async function del (request, response) {
|
|
98
|
+
const options = {
|
|
99
|
+
replayable: isFlagSet(request.query, 'replayable'),
|
|
100
|
+
removeProxies: isFlagSet(request.query, 'removeProxies')
|
|
101
|
+
},
|
|
102
|
+
imposter = await imposters.get(request.params.id);
|
|
103
|
+
|
|
104
|
+
if (imposter) {
|
|
105
|
+
const json = await imposter.toJSON(options);
|
|
106
|
+
await imposters.del(request.params.id);
|
|
107
|
+
response.send(json);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
response.send({});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The function responding to POST /imposters/:id/_requests
|
|
116
|
+
* This is what protocol implementations call to send the JSON request
|
|
117
|
+
* structure to mountebank, which responds with the JSON response structure
|
|
118
|
+
* @memberOf module:controllers/imposterController#
|
|
119
|
+
* @param {Object} request - the HTTP request
|
|
120
|
+
* @param {Object} response - the HTTP response
|
|
121
|
+
* @returns {Object} - the promise
|
|
122
|
+
*/
|
|
123
|
+
async function postRequest (request, response) {
|
|
124
|
+
const imposter = await imposters.get(request.params.id),
|
|
125
|
+
jsonResponse = await imposter.getResponseFor(request.body.request);
|
|
126
|
+
|
|
127
|
+
response.send(jsonResponse);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The function responding to POST /imposters/:id/_requests/:proxyResolutionKey
|
|
132
|
+
* This is what protocol implementations call after proxying a request so
|
|
133
|
+
* mountebank can record the response and add behaviors to
|
|
134
|
+
* @memberOf module:controllers/imposterController#
|
|
135
|
+
* @param {Object} request - the HTTP request
|
|
136
|
+
* @param {Object} response - the HTTP response
|
|
137
|
+
* @returns {Object} - the promise
|
|
138
|
+
*/
|
|
139
|
+
async function postProxyResponse (request, response) {
|
|
140
|
+
const proxyResolutionKey = request.params.proxyResolutionKey,
|
|
141
|
+
proxyResponse = request.body.proxyResponse,
|
|
142
|
+
imposter = await imposters.get(request.params.id),
|
|
143
|
+
json = await imposter.getProxyResponseFor(proxyResponse, proxyResolutionKey);
|
|
144
|
+
|
|
145
|
+
response.send(json);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function validateStubs (imposter, newStubs) {
|
|
149
|
+
const errors = [];
|
|
150
|
+
|
|
151
|
+
if (!helpers.defined(newStubs)) {
|
|
152
|
+
errors.push(exceptions.ValidationError("'stubs' is a required field"));
|
|
153
|
+
}
|
|
154
|
+
else if (!Array.isArray(newStubs)) {
|
|
155
|
+
errors.push(exceptions.ValidationError("'stubs' must be an array"));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (errors.length > 0) {
|
|
159
|
+
return Promise.resolve({ isValid: false, errors });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const request = await imposter.toJSON(),
|
|
163
|
+
Protocol = protocols[request.protocol],
|
|
164
|
+
validator = dryRunValidator.create({
|
|
165
|
+
testRequest: Protocol.testRequest,
|
|
166
|
+
testProxyResponse: Protocol.testProxyResponse,
|
|
167
|
+
additionalValidation: Protocol.validate,
|
|
168
|
+
allowInjection: allowInjection
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
request.stubs = newStubs;
|
|
172
|
+
compatibility.upcast(request);
|
|
173
|
+
return validator.validate(request, logger);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function respondWithValidationErrors (response, validationErrors, statusCode = 400) {
|
|
177
|
+
logger.error(`error changing stubs: ${JSON.stringify(exceptions.details(validationErrors))}`);
|
|
178
|
+
response.statusCode = statusCode;
|
|
179
|
+
response.send({ errors: validationErrors });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The function responding to PUT /imposters/:id/stubs
|
|
184
|
+
* Overwrites the stubs list without restarting the imposter
|
|
185
|
+
* @memberOf module:controllers/imposterController#
|
|
186
|
+
* @param {Object} request - the HTTP request
|
|
187
|
+
* @param {Object} response - the HTTP response
|
|
188
|
+
* @returns {Object} - promise for testing
|
|
189
|
+
*/
|
|
190
|
+
async function putStubs (request, response) {
|
|
191
|
+
const imposter = await imposters.get(request.params.id),
|
|
192
|
+
stubs = imposters.stubsFor(request.params.id),
|
|
193
|
+
newStubs = request.body.stubs,
|
|
194
|
+
result = await validateStubs(imposter, newStubs);
|
|
195
|
+
|
|
196
|
+
if (result.isValid) {
|
|
197
|
+
await stubs.overwriteAll(newStubs);
|
|
198
|
+
const json = await imposter.toJSON();
|
|
199
|
+
response.send(json);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
respondWithValidationErrors(response, result.errors);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function validateStubIndex (stubs, index) {
|
|
207
|
+
const allStubs = await stubs.toJSON();
|
|
208
|
+
const errors = [];
|
|
209
|
+
if (typeof allStubs[index] === 'undefined') {
|
|
210
|
+
errors.push(exceptions.ValidationError("'stubIndex' must be a valid integer, representing the array index position of the stub to replace"));
|
|
211
|
+
}
|
|
212
|
+
return { isValid: errors.length === 0, errors };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The function responding to PUT /imposters/:id/stubs/:stubIndex
|
|
217
|
+
* Overwrites a single stub without restarting the imposter
|
|
218
|
+
* @memberOf module:controllers/imposterController#
|
|
219
|
+
* @param {Object} request - the HTTP request
|
|
220
|
+
* @param {Object} response - the HTTP response
|
|
221
|
+
* @returns {Object} - promise for testing
|
|
222
|
+
*/
|
|
223
|
+
async function putStub (request, response) {
|
|
224
|
+
const imposter = await imposters.get(request.params.id),
|
|
225
|
+
stubs = imposters.stubsFor(request.params.id),
|
|
226
|
+
validation = await validateStubIndex(stubs, request.params.stubIndex);
|
|
227
|
+
|
|
228
|
+
if (validation.isValid) {
|
|
229
|
+
const newStub = request.body,
|
|
230
|
+
result = await validateStubs(imposter, [newStub]);
|
|
231
|
+
|
|
232
|
+
if (result.isValid) {
|
|
233
|
+
await stubs.overwriteAtIndex(newStub, request.params.stubIndex);
|
|
234
|
+
const json = await imposter.toJSON();
|
|
235
|
+
response.send(json);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
respondWithValidationErrors(response, result.errors);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
respondWithValidationErrors(response, validation.errors, 404);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function validateNewStub (index, allStubs, newStub) {
|
|
247
|
+
const errors = [];
|
|
248
|
+
|
|
249
|
+
if (typeof index !== 'number' || index < 0 || index > allStubs.length) {
|
|
250
|
+
errors.push(exceptions.ValidationError("'index' must be between 0 and the length of the stubs array"));
|
|
251
|
+
}
|
|
252
|
+
if (typeof newStub === 'undefined') {
|
|
253
|
+
errors.push(exceptions.ValidationError("must contain 'stub' field"));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { isValid: errors.length === 0, errors };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The function responding to POST /imposters/:port/stubs
|
|
261
|
+
* Creates a single stub without restarting the imposter
|
|
262
|
+
* @memberOf module:controllers/imposterController#
|
|
263
|
+
* @param {Object} request - the HTTP request
|
|
264
|
+
* @param {Object} response - the HTTP response
|
|
265
|
+
* @returns {Object} - promise for testing
|
|
266
|
+
*/
|
|
267
|
+
async function postStub (request, response) {
|
|
268
|
+
const imposter = await imposters.get(request.params.id),
|
|
269
|
+
stubs = imposters.stubsFor(request.params.id),
|
|
270
|
+
allStubs = await stubs.toJSON(),
|
|
271
|
+
newStub = request.body.stub,
|
|
272
|
+
index = typeof request.body.index === 'undefined' ? allStubs.length : request.body.index,
|
|
273
|
+
validation = validateNewStub(index, allStubs, newStub);
|
|
274
|
+
|
|
275
|
+
if (validation.isValid) {
|
|
276
|
+
const result = await validateStubs(imposter, [newStub]);
|
|
277
|
+
if (result.isValid) {
|
|
278
|
+
await stubs.insertAtIndex(newStub, index);
|
|
279
|
+
const json = await imposter.toJSON();
|
|
280
|
+
response.send(json);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
respondWithValidationErrors(response, result.errors);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
respondWithValidationErrors(response, validation.errors);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The function responding to DELETE /imposters/:port/stubs/:stubIndex
|
|
293
|
+
* Removes a single stub without restarting the imposter
|
|
294
|
+
* @memberOf module:controllers/imposterController#
|
|
295
|
+
* @param {Object} request - the HTTP request
|
|
296
|
+
* @param {Object} response - the HTTP response
|
|
297
|
+
* @returns {Object} - promise for testing
|
|
298
|
+
*/
|
|
299
|
+
async function deleteStub (request, response) {
|
|
300
|
+
const imposter = await imposters.get(request.params.id),
|
|
301
|
+
stubs = imposters.stubsFor(request.params.id),
|
|
302
|
+
validation = await validateStubIndex(stubs, request.params.stubIndex);
|
|
303
|
+
|
|
304
|
+
if (validation.isValid) {
|
|
305
|
+
await stubs.deleteAtIndex(request.params.stubIndex);
|
|
306
|
+
const json = await imposter.toJSON();
|
|
307
|
+
response.send(json);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
respondWithValidationErrors(response, validation.errors, 404);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
get,
|
|
316
|
+
del,
|
|
317
|
+
resetProxies,
|
|
318
|
+
resetRequests,
|
|
319
|
+
postRequest,
|
|
320
|
+
postProxyResponse,
|
|
321
|
+
putStubs,
|
|
322
|
+
putStub,
|
|
323
|
+
postStub,
|
|
324
|
+
deleteStub
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = { create };
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const exceptions = require('../util/errors.js'),
|
|
4
|
+
helpers = require('../util/helpers.js'),
|
|
5
|
+
compatibility = require('../models/compatibility.js'),
|
|
6
|
+
dryRunValidator = require('../models/dryRunValidator');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The controller that manages the list of imposters
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates the imposters controller
|
|
15
|
+
* @param {Object} protocols - the protocol implementations supported by mountebank
|
|
16
|
+
* @param {Object} imposters - The imposters repository
|
|
17
|
+
* @param {Object} logger - The logger
|
|
18
|
+
* @param {Boolean} allowInjection - Whether injection is allowed or not
|
|
19
|
+
* @returns {{get, post, del, put}}
|
|
20
|
+
*/
|
|
21
|
+
function create (protocols, imposters, logger, allowInjection) {
|
|
22
|
+
function isFlagFalse (query, key) {
|
|
23
|
+
return !helpers.defined(query[key]) || query[key].toLowerCase() !== 'false';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isFlagSet (query, key) {
|
|
27
|
+
return helpers.defined(query[key]) && query[key].toLowerCase() === 'true';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function validatePort (port, errors) {
|
|
31
|
+
const portIsValid = !helpers.defined(port) || (port.toString().indexOf('.') === -1 && port > 0 && port < 65536);
|
|
32
|
+
|
|
33
|
+
if (!portIsValid) {
|
|
34
|
+
errors.push(exceptions.ValidationError("invalid value for 'port'"));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validateProtocol (protocol, errors) {
|
|
39
|
+
const Protocol = protocols[protocol];
|
|
40
|
+
|
|
41
|
+
if (!helpers.defined(protocol)) {
|
|
42
|
+
errors.push(exceptions.ValidationError("'protocol' is a required field"));
|
|
43
|
+
}
|
|
44
|
+
else if (!Protocol) {
|
|
45
|
+
errors.push(exceptions.ValidationError(`the ${protocol} protocol is not yet supported`));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validate (request) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
|
|
52
|
+
compatibility.upcast(request);
|
|
53
|
+
|
|
54
|
+
validatePort(request.port, errors);
|
|
55
|
+
validateProtocol(request.protocol, errors);
|
|
56
|
+
|
|
57
|
+
if (errors.length > 0) {
|
|
58
|
+
return Promise.resolve({ isValid: false, errors });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const Protocol = protocols[request.protocol],
|
|
62
|
+
validator = dryRunValidator.create({
|
|
63
|
+
testRequest: Protocol.testRequest,
|
|
64
|
+
testProxyResponse: Protocol.testProxyResponse,
|
|
65
|
+
additionalValidation: Protocol.validate,
|
|
66
|
+
allowInjection: allowInjection
|
|
67
|
+
});
|
|
68
|
+
return validator.validate(request, logger);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function respondWithValidationErrors (response, validationErrors) {
|
|
73
|
+
logger.error(`error creating imposter: ${JSON.stringify(exceptions.details(validationErrors))}`);
|
|
74
|
+
response.statusCode = 400;
|
|
75
|
+
response.send({ errors: validationErrors });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function respondWithCreationError (response, error) {
|
|
79
|
+
logger.error(`error creating imposter: ${JSON.stringify(exceptions.details(error))}`);
|
|
80
|
+
response.statusCode = (error.code === 'insufficient access') ? 403 : 400;
|
|
81
|
+
response.send({ errors: [error] });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function requestDetails (request) {
|
|
85
|
+
return `${helpers.socketName(request.socket)} => ${JSON.stringify(request.body)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function getAllJSON (queryOptions) {
|
|
89
|
+
const allImposters = await imposters.all(),
|
|
90
|
+
promises = allImposters.map(imposter => imposter.toJSON(queryOptions));
|
|
91
|
+
return Promise.all(promises);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The function responding to GET /imposters
|
|
96
|
+
* @memberOf module:controllers/impostersController#
|
|
97
|
+
* @param {Object} request - the HTTP request
|
|
98
|
+
* @param {Object} response - the HTTP response
|
|
99
|
+
* @returns {Object} - the promise
|
|
100
|
+
*/
|
|
101
|
+
async function get (request, response) {
|
|
102
|
+
const options = {
|
|
103
|
+
replayable: isFlagSet(request.query, 'replayable'),
|
|
104
|
+
removeProxies: isFlagSet(request.query, 'removeProxies'),
|
|
105
|
+
list: !(isFlagSet(request.query, 'replayable') || isFlagSet(request.query, 'removeProxies'))
|
|
106
|
+
},
|
|
107
|
+
impostersJSON = await getAllJSON(options);
|
|
108
|
+
|
|
109
|
+
response.format({
|
|
110
|
+
json: () => response.send({ imposters: impostersJSON }),
|
|
111
|
+
html: () => response.render('imposters', { imposters: impostersJSON })
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The function responding to POST /imposters
|
|
117
|
+
* @memberOf module:controllers/impostersController#
|
|
118
|
+
* @param {Object} request - the HTTP request
|
|
119
|
+
* @param {Object} response - the HTTP response
|
|
120
|
+
* @returns {Object} A promise for testing purposes
|
|
121
|
+
*/
|
|
122
|
+
async function post (request, response) {
|
|
123
|
+
logger.debug(requestDetails(request));
|
|
124
|
+
const validation = await validate(request.body),
|
|
125
|
+
protocol = request.body.protocol;
|
|
126
|
+
|
|
127
|
+
if (validation.isValid) {
|
|
128
|
+
try {
|
|
129
|
+
const imposter = await protocols[protocol].createImposterFrom(request.body);
|
|
130
|
+
await imposters.add(imposter);
|
|
131
|
+
const json = await imposter.toJSON();
|
|
132
|
+
|
|
133
|
+
response.setHeader('Location', imposter.url);
|
|
134
|
+
response.statusCode = 201;
|
|
135
|
+
response.send(json);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
respondWithCreationError(response, error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
respondWithValidationErrors(response, validation.errors);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The function responding to DELETE /imposters
|
|
148
|
+
* @memberOf module:controllers/impostersController#
|
|
149
|
+
* @param {Object} request - the HTTP request
|
|
150
|
+
* @param {Object} response - the HTTP response
|
|
151
|
+
* @returns {Object} A promise for testing purposes
|
|
152
|
+
*/
|
|
153
|
+
async function del (request, response) {
|
|
154
|
+
const options = {
|
|
155
|
+
// default to replayable for backwards compatibility
|
|
156
|
+
replayable: isFlagFalse(request.query, 'replayable'),
|
|
157
|
+
removeProxies: isFlagSet(request.query, 'removeProxies')
|
|
158
|
+
},
|
|
159
|
+
json = await getAllJSON(options);
|
|
160
|
+
|
|
161
|
+
await imposters.deleteAll();
|
|
162
|
+
response.send({ imposters: json });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The function responding to PUT /imposters
|
|
167
|
+
* @memberOf module:controllers/impostersController#
|
|
168
|
+
* @param {Object} request - the HTTP request
|
|
169
|
+
* @param {Object} response - the HTTP response
|
|
170
|
+
* @returns {Object} A promise for testing purposes
|
|
171
|
+
*/
|
|
172
|
+
async function put (request, response) {
|
|
173
|
+
const requestImposters = request.body.imposters || [],
|
|
174
|
+
validationPromises = requestImposters.map(imposter => validate(imposter));
|
|
175
|
+
|
|
176
|
+
logger.debug(requestDetails(request));
|
|
177
|
+
|
|
178
|
+
if (!('imposters' in request.body)) {
|
|
179
|
+
respondWithValidationErrors(response, [
|
|
180
|
+
exceptions.ValidationError("'imposters' is a required field")
|
|
181
|
+
]);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const validations = await Promise.all(validationPromises),
|
|
186
|
+
isValid = validations.every(validation => validation.isValid);
|
|
187
|
+
|
|
188
|
+
if (!isValid) {
|
|
189
|
+
const validationErrors = validations.reduce((accumulator, validation) => accumulator.concat(validation.errors), []);
|
|
190
|
+
respondWithValidationErrors(response, validationErrors);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await imposters.deleteAll();
|
|
195
|
+
try {
|
|
196
|
+
const creationPromises = requestImposters.map(imposter =>
|
|
197
|
+
protocols[imposter.protocol].createImposterFrom(imposter)
|
|
198
|
+
),
|
|
199
|
+
allImposters = await Promise.all(creationPromises);
|
|
200
|
+
await Promise.all(allImposters.map(imposters.add));
|
|
201
|
+
|
|
202
|
+
const promises = allImposters.map(imposter => imposter.toJSON({ list: true })),
|
|
203
|
+
json = await Promise.all(promises);
|
|
204
|
+
response.send({ imposters: json });
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
respondWithCreationError(response, error);
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { get, post, del, put };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = { create };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra'),
|
|
4
|
+
escapeHtml = require('escape-html');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The controller that exposes the logs
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates the logs controller
|
|
13
|
+
* @param {string} logfile - the path to the logfile
|
|
14
|
+
* @returns {{get: get}}
|
|
15
|
+
*/
|
|
16
|
+
function create (logfile) {
|
|
17
|
+
function getLogEntries () {
|
|
18
|
+
if (!logfile || !fs.existsSync(logfile)) {
|
|
19
|
+
return [{ level: 'error', message: 'No logfile' }];
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const entries = fs.readFileSync(logfile).toString().split(/\r?\n/),
|
|
23
|
+
json = '[' + entries.join(',').replace(/,$/, '') + ']';
|
|
24
|
+
return JSON.parse(json);
|
|
25
|
+
}
|
|
26
|
+
catch (ex) {
|
|
27
|
+
return [{ level: 'error', message: 'This page only works for JSON file logging' }];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The function that responds to GET /logs
|
|
33
|
+
* @memberOf module:controllers/logsController#
|
|
34
|
+
* @param {Object} request - the HTTP request
|
|
35
|
+
* @param {Object} response - the HTTP response
|
|
36
|
+
*/
|
|
37
|
+
function get (request, response) {
|
|
38
|
+
const allLogs = getLogEntries(),
|
|
39
|
+
startIndex = parseInt(request.query.startIndex || 0),
|
|
40
|
+
endIndex = parseInt(request.query.endIndex || allLogs.length - 1),
|
|
41
|
+
logs = allLogs.slice(startIndex, endIndex + 1);
|
|
42
|
+
|
|
43
|
+
response.format({
|
|
44
|
+
json: () => response.send({ logs: logs }),
|
|
45
|
+
html: () => response.render('logs', { logs: logs, escape: escapeHtml })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { get };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { create };
|