@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,27 @@
|
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
2
|
+
MIIEpAIBAAKCAQEAzPiVoyRcLURPvvKU1LOhslhSyjBkrGQ1MiRtJ/UIr4STxN36
|
|
3
|
+
L3nCswXUWWVKwIx1j9V8aQy7Kn8txSsw/WxCNmyGmugb90AlIkhIXl7XgZZY5bXe
|
|
4
|
+
EYbqfclp6cJAZg1uBSMVeh5uWjskceNRBnKuCK9K8KUzJB0a2pAipMlQroXD2wHk
|
|
5
|
+
7SZ9o8PUCCQdxtiLFrs4lBZoNkzvdBz1KXqFVRIKDvQfQ7gl90JhQZIt8U5pvR57
|
|
6
|
+
2ewtLUWTOmNmhFqD5c6LBOz3BYw5nfY5kl6NzRA/frQ3OF2qHPYjwaSzAqnug2mQ
|
|
7
|
+
W6h8UhRUurKQSQpanr9PYOOTwtx6l5f0Mc4+1QIDAQABAoIBAA9toq3N/dY2bx47
|
|
8
|
+
WjKMdt5awZiQffNv84Ubss+wJQA5JXpLDxrlul8JUEuOUUsfB3ZVJnEt0STIv+Q4
|
|
9
|
+
dQ6OSImaL6OXVwuMW38yG6hm0Sfi7jwULWv6UMo5D+zVf01vM1nVozc29S17iCm+
|
|
10
|
+
Z4nptenXb/efJ7NPMYdEFCd9M8J/EpvYEMis0/Jtwvlvg7GO1g6fV0BR/HttJM/x
|
|
11
|
+
XBAl9W6LAkL+8/XRJ7iYcCzU6WwUFeCdU/R6kFGngYuMw4r90u6ukWQzuVKXJH7E
|
|
12
|
+
mi4o16UKNzKfnKOyLBbpeJFGF5DhtwoJpHAZVS9bVZQIr6p4ugbvCBuRshlLGDdN
|
|
13
|
+
v9aJfwECgYEA5ZtmxgTgRIHYhLtURDhJo9113C2vPgkbUIxFfKoZPRWkotEYR0M3
|
|
14
|
+
ypgU3mDKn1KMXsrTUq3Uv4+Zvwbym2L3AJCvnyR9FpZoz5U98lLtDDxMSzr9++PA
|
|
15
|
+
dO7QfznQ0L9JDSl8Bav40quuFgwdGdcwvWSFpT5Zzb/fTw+ZfrqfxhUCgYEA5Ig5
|
|
16
|
+
eRAmbk/cmjoNZB0jp8ijJHlvr5QEnXK0cOT9dQ8xk/moNcn0DkSmpaJK/rb3f0+/
|
|
17
|
+
HhOfMnGfABn+7h7g8rnFmwkd9WIxNsQb64+e33NLzdF//MfU5xLE4oHGCTztx8eZ
|
|
18
|
+
ck8RoGB4JvJYcwLt4R/OtL4G6Rfx1JX0pKzthcECgYA2Uyhj3a96RgaGkRQE+BRk
|
|
19
|
+
UveZ2q1Fzj3KNwYR0uUZ0M8dPr+xzLOcmZMGcnw+afeQTgjl3P8jO8Syr+Ai561t
|
|
20
|
+
Us5apvV5rKirxLHdbcVsSa/7dL+3I1Hb2M037OP9H+UW2iPf66p5nekYilEwVfvQ
|
|
21
|
+
M8JzMGdrCOS6/gPhOiKnaQKBgQC9Mcni3+vxB0yqocTUTQtnrELjv2UnBnOLpZqc
|
|
22
|
+
m/b5Ikr5JoaLgVX7Ofp8xY8wsGjVjT+7tqLlMAtiGiNjH007pXBimXmj3FbB8Djt
|
|
23
|
+
G0l71Ae9rOM4cndflbpJiwZYP4jbC/ONHsiI7VSLabawAIzPA3YtS+SMtLYQONUA
|
|
24
|
+
P+mkAQKBgQDRdeTKPPOTF5Md3gdsN4WVdrOkOro+5lCtN9lglRcfrESShV1KtEjz
|
|
25
|
+
WznF3OLO/XFpI30itei1HCZjo+nXSUvsV5t6zBOzQ9tzw2/hgaoH6nooZgdT9r1O
|
|
26
|
+
34YsJhUoDmukCEii8IgRRaZyqS1829xqF8Fw5fgYBr9KgA+FHjpSDw==
|
|
27
|
+
-----END RSA PRIVATE KEY-----
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path'),
|
|
4
|
+
fs = require('fs'),
|
|
5
|
+
https = require('https'),
|
|
6
|
+
baseHttpServer = require('../http/baseHttpServer.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents an https imposter
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function createBaseServer (options) {
|
|
14
|
+
const metadata = {
|
|
15
|
+
key: options.key || fs.readFileSync(path.join(__dirname, '/cert/mb-key.pem'), 'utf8'),
|
|
16
|
+
cert: options.cert || fs.readFileSync(path.join(__dirname, '/cert/mb-cert.pem'), 'utf8'),
|
|
17
|
+
mutualAuth: Boolean(options.mutualAuth),
|
|
18
|
+
rejectUnauthorized: options.rejectUnauthorized || false,
|
|
19
|
+
ca: options.ca || null
|
|
20
|
+
},
|
|
21
|
+
// client certs will not reject the request. It does set the request.client.authorized variable
|
|
22
|
+
// to false for all self-signed certs; use rejectUnauthorized: true and a ca: field set to an array
|
|
23
|
+
// containing the client cert to see request.client.authorized = true
|
|
24
|
+
config = {
|
|
25
|
+
key: metadata.key,
|
|
26
|
+
cert: metadata.cert,
|
|
27
|
+
mutualAuth: metadata.mutualAuth,
|
|
28
|
+
rejectUnauthorized: metadata.rejectUnauthorized,
|
|
29
|
+
ca: metadata.ca,
|
|
30
|
+
requestCert: metadata.mutualAuth && metadata.rejectUnauthorized
|
|
31
|
+
},
|
|
32
|
+
createNodeServer = () => https.createServer(config);
|
|
33
|
+
|
|
34
|
+
if (options.ciphers) {
|
|
35
|
+
metadata.ciphers = options.ciphers.toUpperCase();
|
|
36
|
+
config.ciphers = options.ciphers.toUpperCase();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { metadata, createNodeServer };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = baseHttpServer(createBaseServer);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const config = JSON.parse(process.argv[2]),
|
|
4
|
+
httpsServer = require('./httpsServer.js'),
|
|
5
|
+
httpProxy = require('../http/httpProxy.js'),
|
|
6
|
+
mbConnection = require('../mbConnection.js').create(config);
|
|
7
|
+
|
|
8
|
+
httpsServer.create(config, mbConnection.logger(), mbConnection.getResponse).then(server => {
|
|
9
|
+
mbConnection.setPort(server.port);
|
|
10
|
+
mbConnection.setProxy(httpProxy.create(mbConnection.logger()));
|
|
11
|
+
|
|
12
|
+
const metadata = server.metadata;
|
|
13
|
+
metadata.port = server.port;
|
|
14
|
+
console.log(JSON.stringify(metadata));
|
|
15
|
+
}).catch(error => {
|
|
16
|
+
console.error(JSON.stringify(error));
|
|
17
|
+
process.exit(1); // eslint-disable-line no-process-exit
|
|
18
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An imposter represents a protocol listening on a socket. Most imposter
|
|
5
|
+
* functionality is in each particular protocol implementation. This module
|
|
6
|
+
* exists as a bridge between the API and the protocol, mapping back to pretty
|
|
7
|
+
* JSON for the end user.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const prometheus = require('prom-client'),
|
|
12
|
+
compatibility = require('./compatibility.js'),
|
|
13
|
+
scopedLogger = require('../util/scopedLogger.js'),
|
|
14
|
+
helpers = require('../util/helpers.js'),
|
|
15
|
+
predicates = require('./predicates.js'),
|
|
16
|
+
imposterPrinter = require('./imposterPrinter.js'),
|
|
17
|
+
cachedObjects = {};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create the imposter
|
|
21
|
+
* @param {Object} Protocol - The protocol factory for creating servers of that protocol
|
|
22
|
+
* @param {Object} creationRequest - the parsed imposter JSON
|
|
23
|
+
* @param {Object} baseLogger - the logger
|
|
24
|
+
* @param {Object} config - command line options
|
|
25
|
+
* @param {Function} isAllowedConnection - function to determine if the IP address of the requestor is allowed
|
|
26
|
+
* @returns {Object}
|
|
27
|
+
*/
|
|
28
|
+
async function create (Protocol, creationRequest, baseLogger, config, isAllowedConnection) {
|
|
29
|
+
|
|
30
|
+
const metrics = getMetrics();
|
|
31
|
+
|
|
32
|
+
function scopeFor (port) {
|
|
33
|
+
let scope = `${creationRequest.protocol}:${port}`;
|
|
34
|
+
|
|
35
|
+
if (creationRequest.name) {
|
|
36
|
+
scope += ' ' + creationRequest.name;
|
|
37
|
+
}
|
|
38
|
+
return scope;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const logger = scopedLogger.create(baseLogger, scopeFor(creationRequest.port)),
|
|
42
|
+
imposterState = {},
|
|
43
|
+
unresolvedProxies = {},
|
|
44
|
+
header = helpers.clone(creationRequest);
|
|
45
|
+
|
|
46
|
+
// Free up the memory by allowing garbage collection of stubs when using filesystemBackedImpostersRepository
|
|
47
|
+
delete header.stubs;
|
|
48
|
+
|
|
49
|
+
let stubs;
|
|
50
|
+
let resolver;
|
|
51
|
+
let encoding;
|
|
52
|
+
let numberOfRequests = 0;
|
|
53
|
+
|
|
54
|
+
compatibility.upcast(creationRequest);
|
|
55
|
+
|
|
56
|
+
// If the CLI --mock flag is passed, we record even if the imposter level recordRequests = false
|
|
57
|
+
const recordRequests = config.recordRequests || creationRequest.recordRequests;
|
|
58
|
+
|
|
59
|
+
async function findFirstMatch (request) {
|
|
60
|
+
const filter = stubPredicates => {
|
|
61
|
+
return stubPredicates.every(predicate =>
|
|
62
|
+
predicates.evaluate(predicate, request, encoding, logger, imposterState));
|
|
63
|
+
},
|
|
64
|
+
observePredicateMatchDuration = metrics.predicateMatchDuration.startTimer(),
|
|
65
|
+
match = await stubs.first(filter);
|
|
66
|
+
|
|
67
|
+
observePredicateMatchDuration({ imposter: logger.scopePrefix });
|
|
68
|
+
if (match.success) {
|
|
69
|
+
logger.debug(`using predicate match: ${JSON.stringify(match.stub.predicates || {})}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
metrics.noMatchCount.inc({ imposter: logger.scopePrefix });
|
|
73
|
+
logger.info('no predicate match, using default response');
|
|
74
|
+
}
|
|
75
|
+
return match;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function recordMatch (stub, request, response, responseConfig, start) {
|
|
79
|
+
if (response.proxy) {
|
|
80
|
+
// Out of process proxying, so we don't have the actual response yet.
|
|
81
|
+
const parts = response.callbackURL.split('/'),
|
|
82
|
+
proxyResolutionKey = parts[parts.length - 1];
|
|
83
|
+
|
|
84
|
+
unresolvedProxies[proxyResolutionKey] = {
|
|
85
|
+
recordMatch: proxyResponse => {
|
|
86
|
+
return stub.recordMatch(request, proxyResponse, responseConfig, new Date() - start);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
else if (response.response) {
|
|
91
|
+
// Out of process responses wrap the result in an outer response object
|
|
92
|
+
await stub.recordMatch(request, response.response, responseConfig, new Date() - start);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// In process resolution
|
|
96
|
+
await stub.recordMatch(request, response, responseConfig, new Date() - start);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// requestDetails are not stored with the imposter
|
|
101
|
+
// It was created to pass the raw URL to maintain the exact querystring during http proxying
|
|
102
|
+
// without having to change the path / query options on the stored request
|
|
103
|
+
async function getResponseFor (request, requestDetails) {
|
|
104
|
+
if (!isAllowedConnection(request.ip, logger)) {
|
|
105
|
+
metrics.blockedIPCount.inc({ imposter: logger.scopePrefix });
|
|
106
|
+
return { blocked: true, code: 'unauthorized ip address' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const start = new Date();
|
|
110
|
+
|
|
111
|
+
metrics.requestCount.inc({ imposter: logger.scopePrefix });
|
|
112
|
+
numberOfRequests += 1;
|
|
113
|
+
if (recordRequests) {
|
|
114
|
+
await stubs.addRequest(request);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const match = await findFirstMatch(request),
|
|
118
|
+
observeResponseGenerationDuration = metrics.responseGenerationDuration.startTimer(),
|
|
119
|
+
responseConfig = await match.stub.nextResponse();
|
|
120
|
+
|
|
121
|
+
logger.debug(`generating response from ${JSON.stringify(responseConfig)}`);
|
|
122
|
+
const response = await resolver.resolve(responseConfig, request, logger, imposterState, requestDetails);
|
|
123
|
+
observeResponseGenerationDuration({ imposter: logger.scopePrefix });
|
|
124
|
+
|
|
125
|
+
if (config.recordMatches) {
|
|
126
|
+
await recordMatch(match.stub, request, response, responseConfig, start);
|
|
127
|
+
}
|
|
128
|
+
return response;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function getProxyResponseFor (proxyResponse, proxyResolutionKey) {
|
|
132
|
+
const response = await resolver.resolveProxy(proxyResponse, proxyResolutionKey, logger, imposterState);
|
|
133
|
+
if (config.recordMatches && unresolvedProxies[String(proxyResolutionKey)].recordMatch) {
|
|
134
|
+
await unresolvedProxies[String(proxyResolutionKey)].recordMatch(response);
|
|
135
|
+
}
|
|
136
|
+
delete unresolvedProxies[String(proxyResolutionKey)];
|
|
137
|
+
return response;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function resetRequests () {
|
|
141
|
+
await stubs.deleteSavedRequests();
|
|
142
|
+
numberOfRequests = 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getMetrics () {
|
|
146
|
+
return metricsAlreadyCreated() ? getCreatedMetrics() : createImposterMetrics();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function metricsAlreadyCreated () {
|
|
150
|
+
return Boolean(cachedObjects.metrics);
|
|
151
|
+
}
|
|
152
|
+
function getCreatedMetrics () {
|
|
153
|
+
return cachedObjects.metrics;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createImposterMetrics () {
|
|
157
|
+
const impostersMetrics = {
|
|
158
|
+
predicateMatchDuration: new prometheus.Histogram({
|
|
159
|
+
name: 'mb_predicate_match_duration_seconds',
|
|
160
|
+
help: 'Time it takes to match the predicates and select a stub',
|
|
161
|
+
buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 1],
|
|
162
|
+
labelNames: ['imposter']
|
|
163
|
+
}),
|
|
164
|
+
noMatchCount: new prometheus.Counter({
|
|
165
|
+
name: 'mb_no_match_total',
|
|
166
|
+
help: 'Number of times no stub matched the request',
|
|
167
|
+
labelNames: ['imposter']
|
|
168
|
+
}),
|
|
169
|
+
requestCount: new prometheus.Counter({
|
|
170
|
+
name: 'mb_request_total',
|
|
171
|
+
help: 'Number of requests to the imposter',
|
|
172
|
+
labelNames: ['imposter']
|
|
173
|
+
}),
|
|
174
|
+
responseGenerationDuration: new prometheus.Histogram({
|
|
175
|
+
name: 'mb_response_generation_duration_seconds',
|
|
176
|
+
help: 'Time it takes to generate the response from a stub',
|
|
177
|
+
buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 1, 3, 5, 10, 30],
|
|
178
|
+
labelNames: ['imposter']
|
|
179
|
+
}),
|
|
180
|
+
blockedIPCount: new prometheus.Counter({
|
|
181
|
+
name: 'mb_blocked_ip_total',
|
|
182
|
+
help: 'Number of times a connection was blocked from a non-whitelisted IP address',
|
|
183
|
+
labelNames: ['imposter']
|
|
184
|
+
})
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
cachedObjects.metrics = impostersMetrics;
|
|
188
|
+
|
|
189
|
+
return cachedObjects.metrics;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
try {
|
|
194
|
+
if (!helpers.defined(creationRequest.host) && helpers.defined(config.host)) {
|
|
195
|
+
creationRequest.host = config.host;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
Protocol.createServer(creationRequest, logger, getResponseFor).then(server => {
|
|
199
|
+
if (creationRequest.port !== server.port) {
|
|
200
|
+
creationRequest.port = server.port;
|
|
201
|
+
logger.changeScope(scopeFor(server.port));
|
|
202
|
+
}
|
|
203
|
+
logger.info('Open for business...');
|
|
204
|
+
|
|
205
|
+
stubs = server.stubs;
|
|
206
|
+
resolver = server.resolver;
|
|
207
|
+
encoding = server.encoding;
|
|
208
|
+
|
|
209
|
+
function stop () {
|
|
210
|
+
return new Promise(closed => {
|
|
211
|
+
server.close(() => {
|
|
212
|
+
logger.info('Ciao for now');
|
|
213
|
+
closed();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function loadRequests () {
|
|
219
|
+
return recordRequests ? stubs.loadRequests() : [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const printer = imposterPrinter.create(header, server, loadRequests),
|
|
223
|
+
toJSON = options => printer.toJSON(numberOfRequests, options);
|
|
224
|
+
|
|
225
|
+
return resolve({
|
|
226
|
+
port: server.port,
|
|
227
|
+
url: '/imposters/' + server.port,
|
|
228
|
+
creationRequest: creationRequest,
|
|
229
|
+
toJSON,
|
|
230
|
+
stop,
|
|
231
|
+
getResponseFor,
|
|
232
|
+
getProxyResponseFor,
|
|
233
|
+
resetRequests
|
|
234
|
+
});
|
|
235
|
+
}, reject);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
reject(error);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = { create };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const helpers = require('../util/helpers.js');
|
|
4
|
+
|
|
5
|
+
function create (header, server, loadRequests) {
|
|
6
|
+
const baseURL = `/imposters/${server.port}`;
|
|
7
|
+
|
|
8
|
+
function createHeader (numberOfRequests, options) {
|
|
9
|
+
const result = {
|
|
10
|
+
protocol: header.protocol,
|
|
11
|
+
port: server.port
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (header.name) {
|
|
15
|
+
result.name = header.name;
|
|
16
|
+
}
|
|
17
|
+
if (header.defaultResponse) {
|
|
18
|
+
result.defaultResponse = header.defaultResponse;
|
|
19
|
+
}
|
|
20
|
+
if (!options.replayable) {
|
|
21
|
+
result.numberOfRequests = numberOfRequests;
|
|
22
|
+
}
|
|
23
|
+
if (!options.list) {
|
|
24
|
+
result.recordRequests = Boolean(header.recordRequests);
|
|
25
|
+
|
|
26
|
+
Object.keys(server.metadata).forEach(key => {
|
|
27
|
+
result[key] = server.metadata[key];
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (header.endOfRequestResolver) {
|
|
31
|
+
result.endOfRequestResolver = header.endOfRequestResolver;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function addStubsTo (imposter, options) {
|
|
38
|
+
const newOptions = {};
|
|
39
|
+
if (!options.replayable) {
|
|
40
|
+
newOptions.debug = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
imposter.stubs = await server.stubs.toJSON(newOptions);
|
|
44
|
+
return imposter;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function removeNonEssentialInformationFrom (imposter) {
|
|
48
|
+
imposter.stubs.forEach(stub => {
|
|
49
|
+
stub.responses.forEach(response => {
|
|
50
|
+
if (helpers.defined(response.is)) {
|
|
51
|
+
delete response.is._proxyResponseTime;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function addRequestsTo (imposter) {
|
|
58
|
+
imposter.requests = await loadRequests();
|
|
59
|
+
return imposter;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function removeProxiesFrom (imposter) {
|
|
63
|
+
imposter.stubs.forEach(stub => {
|
|
64
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
65
|
+
stub.responses = stub.responses.filter(response => !response.hasOwnProperty('proxy'));
|
|
66
|
+
});
|
|
67
|
+
imposter.stubs = imposter.stubs.filter(stub => stub.responses.length > 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function addLinksTo (imposter) {
|
|
71
|
+
imposter._links = {
|
|
72
|
+
self: { href: baseURL },
|
|
73
|
+
stubs: { href: `${baseURL}/stubs` }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (imposter.stubs) {
|
|
77
|
+
for (let i = 0; i < imposter.stubs.length; i += 1) {
|
|
78
|
+
imposter.stubs[i]._links = { self: { href: `${baseURL}/stubs/${i}` } };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function toJSON (numberOfRequests, options = {}) {
|
|
84
|
+
// I consider the order of fields represented important. They won't matter for parsing,
|
|
85
|
+
// but it makes a nicer user experience for developers viewing the JSON to keep the most
|
|
86
|
+
// relevant information at the top. Some of the order of operations in this file represents
|
|
87
|
+
// that (e.g. keeping the _links at the end), and is tested with some documentation tests.
|
|
88
|
+
const result = createHeader(numberOfRequests, options);
|
|
89
|
+
|
|
90
|
+
options = options || {};
|
|
91
|
+
|
|
92
|
+
if (options.list) {
|
|
93
|
+
addLinksTo(result);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!options.replayable) {
|
|
98
|
+
await addRequestsTo(result);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await addStubsTo(result, options);
|
|
102
|
+
|
|
103
|
+
if (options.replayable) {
|
|
104
|
+
removeNonEssentialInformationFrom(result);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
addLinksTo(result);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (options.removeProxies) {
|
|
111
|
+
removeProxiesFrom(result);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { toJSON };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { create };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra'),
|
|
4
|
+
path = require('path'),
|
|
5
|
+
fileSystemBackedImpostersRepository = require('./filesystemBackedImpostersRepository.js'),
|
|
6
|
+
inMemoryImpostersRepository = require('./inMemoryImpostersRepository.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An factory abstraction for loading imposters
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates the repository based on startup configuration
|
|
15
|
+
* @param {Object} config - The startup configuration
|
|
16
|
+
* @param {Object} logger - The logger
|
|
17
|
+
* @returns {Object} - the repository
|
|
18
|
+
*/
|
|
19
|
+
function create (config, logger) {
|
|
20
|
+
if (config.impostersRepository) {
|
|
21
|
+
const filename = path.resolve(path.relative(process.cwd(), config.impostersRepository));
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(filename)) {
|
|
24
|
+
try {
|
|
25
|
+
return require(filename).create(config, logger);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
logger.error(`An error occured while creating custom impostersRepository:\n ${e}`);
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
logger.warn(`Imposters Respository ${filename} does not exist. The default will be used`);
|
|
34
|
+
return this.inMemory();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (config.datadir) {
|
|
38
|
+
return fileSystemBackedImpostersRepository.create(config, logger);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return this.inMemory();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function inMemory () {
|
|
46
|
+
return inMemoryImpostersRepository.create();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { create, inMemory };
|