@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,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 };