@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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/bin/mb +136 -0
  4. package/package.json +71 -0
  5. package/releases.json +52 -0
  6. package/src/cli/api.js +112 -0
  7. package/src/cli/cli.js +420 -0
  8. package/src/controllers/configController.js +64 -0
  9. package/src/controllers/feedController.js +115 -0
  10. package/src/controllers/homeController.js +58 -0
  11. package/src/controllers/imposterController.js +328 -0
  12. package/src/controllers/impostersController.js +215 -0
  13. package/src/controllers/logsController.js +52 -0
  14. package/src/models/behaviors.js +553 -0
  15. package/src/models/behaviorsValidator.js +186 -0
  16. package/src/models/compatibility.js +133 -0
  17. package/src/models/dryRunValidator.js +261 -0
  18. package/src/models/filesystemBackedImpostersRepository.js +908 -0
  19. package/src/models/http/baseHttpServer.js +207 -0
  20. package/src/models/http/headersMap.js +87 -0
  21. package/src/models/http/httpProxy.js +230 -0
  22. package/src/models/http/httpRequest.js +82 -0
  23. package/src/models/http/httpServer.js +18 -0
  24. package/src/models/http/index.js +18 -0
  25. package/src/models/https/cert/mb-cert.pem +20 -0
  26. package/src/models/https/cert/mb-csr.pem +16 -0
  27. package/src/models/https/cert/mb-key.pem +27 -0
  28. package/src/models/https/httpsServer.js +42 -0
  29. package/src/models/https/index.js +18 -0
  30. package/src/models/imposter.js +243 -0
  31. package/src/models/imposterPrinter.js +120 -0
  32. package/src/models/impostersRepository.js +49 -0
  33. package/src/models/inMemoryImpostersRepository.js +418 -0
  34. package/src/models/jsonpath.js +44 -0
  35. package/src/models/mbConnection.js +107 -0
  36. package/src/models/predicates.js +438 -0
  37. package/src/models/protocols.js +242 -0
  38. package/src/models/responseResolver.js +398 -0
  39. package/src/models/smtp/index.js +16 -0
  40. package/src/models/smtp/smtpRequest.js +60 -0
  41. package/src/models/smtp/smtpServer.js +109 -0
  42. package/src/models/tcp/index.js +18 -0
  43. package/src/models/tcp/tcpProxy.js +110 -0
  44. package/src/models/tcp/tcpRequest.js +23 -0
  45. package/src/models/tcp/tcpServer.js +156 -0
  46. package/src/models/tcp/tcpValidator.js +19 -0
  47. package/src/models/xpath.js +95 -0
  48. package/src/mountebank.js +245 -0
  49. package/src/public/images/arrow_down.png +0 -0
  50. package/src/public/images/arrow_up.png +0 -0
  51. package/src/public/images/book.jpg +0 -0
  52. package/src/public/images/dataflow.png +0 -0
  53. package/src/public/images/favicon.ico +0 -0
  54. package/src/public/images/forkme_right_orange_ff7600.png +0 -0
  55. package/src/public/images/mountebank.png +0 -0
  56. package/src/public/images/overview.gif +0 -0
  57. package/src/public/images/quote.png +0 -0
  58. package/src/public/images/tw-logo.png +0 -0
  59. package/src/public/scripts/jquery/jquery-3.6.1.min.js +2 -0
  60. package/src/public/scripts/urlHashHandler.js +31 -0
  61. package/src/public/stylesheets/application.css +424 -0
  62. package/src/public/stylesheets/ie.css +14 -0
  63. package/src/public/stylesheets/imposters.css +121 -0
  64. package/src/public/stylesheets/jqueryui/1.10.4/themes/smoothness/jquery-ui.css +1178 -0
  65. package/src/util/combinators.js +68 -0
  66. package/src/util/date.js +51 -0
  67. package/src/util/errors.js +55 -0
  68. package/src/util/helpers.js +131 -0
  69. package/src/util/inherit.js +28 -0
  70. package/src/util/ip.js +54 -0
  71. package/src/util/logger.js +83 -0
  72. package/src/util/middleware.js +256 -0
  73. package/src/util/scopedLogger.js +47 -0
  74. package/src/views/_footer.ejs +20 -0
  75. package/src/views/_header.ejs +113 -0
  76. package/src/views/_imposter.ejs +8 -0
  77. package/src/views/config.ejs +71 -0
  78. package/src/views/docs/api/behaviors/copy.ejs +427 -0
  79. package/src/views/docs/api/behaviors/decorate.ejs +182 -0
  80. package/src/views/docs/api/behaviors/lookup.ejs +220 -0
  81. package/src/views/docs/api/behaviors/shellTransform.ejs +153 -0
  82. package/src/views/docs/api/behaviors/wait.ejs +121 -0
  83. package/src/views/docs/api/behaviors.ejs +141 -0
  84. package/src/views/docs/api/contracts/addStub-description.ejs +10 -0
  85. package/src/views/docs/api/contracts/addStub.ejs +10 -0
  86. package/src/views/docs/api/contracts/config-description.ejs +32 -0
  87. package/src/views/docs/api/contracts/config.ejs +23 -0
  88. package/src/views/docs/api/contracts/home-description.ejs +18 -0
  89. package/src/views/docs/api/contracts/home.ejs +13 -0
  90. package/src/views/docs/api/contracts/imposter-description.ejs +439 -0
  91. package/src/views/docs/api/contracts/imposter.ejs +182 -0
  92. package/src/views/docs/api/contracts/imposters-description.ejs +13 -0
  93. package/src/views/docs/api/contracts/imposters.ejs +13 -0
  94. package/src/views/docs/api/contracts/logs-description.ejs +3 -0
  95. package/src/views/docs/api/contracts/logs.ejs +14 -0
  96. package/src/views/docs/api/contracts/stub-description.ejs +4 -0
  97. package/src/views/docs/api/contracts/stub.ejs +7 -0
  98. package/src/views/docs/api/contracts/stubs-description.ejs +4 -0
  99. package/src/views/docs/api/contracts/stubs.ejs +11 -0
  100. package/src/views/docs/api/contracts.ejs +133 -0
  101. package/src/views/docs/api/errors.ejs +64 -0
  102. package/src/views/docs/api/fault/connectionReset.ejs +31 -0
  103. package/src/views/docs/api/fault/randomDataThenClose.ejs +31 -0
  104. package/src/views/docs/api/faults.ejs +57 -0
  105. package/src/views/docs/api/injection.ejs +426 -0
  106. package/src/views/docs/api/json.ejs +205 -0
  107. package/src/views/docs/api/jsonpath.ejs +210 -0
  108. package/src/views/docs/api/mocks.ejs +130 -0
  109. package/src/views/docs/api/overview.ejs +968 -0
  110. package/src/views/docs/api/predicates/and.ejs +62 -0
  111. package/src/views/docs/api/predicates/contains.ejs +64 -0
  112. package/src/views/docs/api/predicates/deepEquals.ejs +114 -0
  113. package/src/views/docs/api/predicates/endsWith.ejs +66 -0
  114. package/src/views/docs/api/predicates/equals.ejs +125 -0
  115. package/src/views/docs/api/predicates/exists.ejs +118 -0
  116. package/src/views/docs/api/predicates/inject.ejs +67 -0
  117. package/src/views/docs/api/predicates/matches.ejs +66 -0
  118. package/src/views/docs/api/predicates/not.ejs +52 -0
  119. package/src/views/docs/api/predicates/or.ejs +79 -0
  120. package/src/views/docs/api/predicates/startsWith.ejs +62 -0
  121. package/src/views/docs/api/predicates.ejs +382 -0
  122. package/src/views/docs/api/proxies.ejs +191 -0
  123. package/src/views/docs/api/proxy/addDecorateBehavior.ejs +115 -0
  124. package/src/views/docs/api/proxy/addWaitBehavior.ejs +96 -0
  125. package/src/views/docs/api/proxy/injectHeaders.ejs +91 -0
  126. package/src/views/docs/api/proxy/predicateGenerators.ejs +600 -0
  127. package/src/views/docs/api/proxy/proxyModes.ejs +495 -0
  128. package/src/views/docs/api/stubs.ejs +391 -0
  129. package/src/views/docs/api/xpath.ejs +281 -0
  130. package/src/views/docs/cli/configFiles.ejs +133 -0
  131. package/src/views/docs/cli/customFormatters.ejs +53 -0
  132. package/src/views/docs/cli/help.ejs +6 -0
  133. package/src/views/docs/cli/replay.ejs +42 -0
  134. package/src/views/docs/cli/restart.ejs +10 -0
  135. package/src/views/docs/cli/save.ejs +68 -0
  136. package/src/views/docs/cli/start.ejs +234 -0
  137. package/src/views/docs/cli/stop.ejs +32 -0
  138. package/src/views/docs/commandLine.ejs +93 -0
  139. package/src/views/docs/communityExtensions.ejs +233 -0
  140. package/src/views/docs/gettingStarted.ejs +146 -0
  141. package/src/views/docs/mentalModel.ejs +51 -0
  142. package/src/views/docs/protocols/custom.ejs +231 -0
  143. package/src/views/docs/protocols/http.ejs +238 -0
  144. package/src/views/docs/protocols/https.ejs +246 -0
  145. package/src/views/docs/protocols/smtp.ejs +142 -0
  146. package/src/views/docs/protocols/tcp.ejs +431 -0
  147. package/src/views/docs/security.ejs +38 -0
  148. package/src/views/faqs.ejs +65 -0
  149. package/src/views/feed.ejs +33 -0
  150. package/src/views/imposter.ejs +22 -0
  151. package/src/views/imposters.ejs +33 -0
  152. package/src/views/index.ejs +89 -0
  153. package/src/views/license.ejs +30 -0
  154. package/src/views/logs.ejs +77 -0
  155. package/src/views/releases/v1.1.0.ejs +55 -0
  156. package/src/views/releases/v1.1.36.ejs +84 -0
  157. package/src/views/releases/v1.1.72.ejs +92 -0
  158. package/src/views/releases/v1.10.0.ejs +108 -0
  159. package/src/views/releases/v1.11.0.ejs +109 -0
  160. package/src/views/releases/v1.12.0.ejs +96 -0
  161. package/src/views/releases/v1.13.0.ejs +118 -0
  162. package/src/views/releases/v1.14.0.ejs +107 -0
  163. package/src/views/releases/v1.14.1.ejs +94 -0
  164. package/src/views/releases/v1.15.0.ejs +113 -0
  165. package/src/views/releases/v1.16.0.ejs +104 -0
  166. package/src/views/releases/v1.2.0.ejs +78 -0
  167. package/src/views/releases/v1.2.103.ejs +86 -0
  168. package/src/views/releases/v1.2.122.ejs +86 -0
  169. package/src/views/releases/v1.2.30.ejs +84 -0
  170. package/src/views/releases/v1.2.45.ejs +84 -0
  171. package/src/views/releases/v1.2.56.ejs +79 -0
  172. package/src/views/releases/v1.3.0.ejs +86 -0
  173. package/src/views/releases/v1.3.1.ejs +100 -0
  174. package/src/views/releases/v1.4.0.ejs +96 -0
  175. package/src/views/releases/v1.4.1.ejs +103 -0
  176. package/src/views/releases/v1.4.2.ejs +100 -0
  177. package/src/views/releases/v1.4.3.ejs +113 -0
  178. package/src/views/releases/v1.5.0.ejs +104 -0
  179. package/src/views/releases/v1.5.1.ejs +91 -0
  180. package/src/views/releases/v1.6.0.ejs +109 -0
  181. package/src/views/releases/v1.7.0.ejs +113 -0
  182. package/src/views/releases/v1.7.1.ejs +90 -0
  183. package/src/views/releases/v1.7.2.ejs +96 -0
  184. package/src/views/releases/v1.8.0.ejs +121 -0
  185. package/src/views/releases/v1.9.0.ejs +111 -0
  186. package/src/views/releases/v2.0.0.ejs +159 -0
  187. package/src/views/releases/v2.1.0.ejs +121 -0
  188. package/src/views/releases/v2.1.1.ejs +106 -0
  189. package/src/views/releases/v2.1.2.ejs +84 -0
  190. package/src/views/releases/v2.2.0.ejs +115 -0
  191. package/src/views/releases/v2.2.1.ejs +102 -0
  192. package/src/views/releases/v2.3.0.ejs +121 -0
  193. package/src/views/releases/v2.3.1.ejs +100 -0
  194. package/src/views/releases/v2.3.2.ejs +102 -0
  195. package/src/views/releases/v2.3.3.ejs +97 -0
  196. package/src/views/releases/v2.4.0.ejs +114 -0
  197. package/src/views/releases/v2.5.0.ejs +51 -0
  198. package/src/views/releases/v2.6.0.ejs +35 -0
  199. package/src/views/releases/v2.7.0.ejs +32 -0
  200. package/src/views/releases/v2.8.0.ejs +36 -0
  201. package/src/views/releases/v2.8.1.ejs +7 -0
  202. package/src/views/releases/v2.8.2.ejs +26 -0
  203. package/src/views/releases/v2.9.0.ejs +32 -0
  204. package/src/views/releases/v2.9.1.ejs +10 -0
  205. package/src/views/releases.ejs +26 -0
  206. package/src/views/sitemap.ejs +36 -0
  207. 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 };