@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,600 @@
1
+ <p>Recording a rich set of test data through proxying requires also capturing the
2
+ appropriate predicates from the request, so that saved responses are only replayed
3
+ when the requests are similar. The <code>predicateGenerators</code> field defines the
4
+ template for the generated predicates. Each object in the <code>predicateGenerators</code>
5
+ array takes the following fields:</p>
6
+
7
+ <table>
8
+ <tr>
9
+ <th>Parameter</th>
10
+ <th style='min-width: 4em;'>Default</th>
11
+ <th>Type</th>
12
+ <th>Description</th>
13
+ </tr>
14
+ <tr>
15
+ <td><code>matches</code></td>
16
+ <td><code>{}</code></td>
17
+ <td>object</td>
18
+ <td>The fields that need to be equal in subsequent requests to replay the saved response.
19
+ Set the field value <code>true</code> to generate a predicate based on it. Nested fields,
20
+ as in JSON fields or HTTP headers, are supported as well, as long as the leaf keys have
21
+ a <code>true</code> value. If you set the parent object key (e.g. <code>query</code>)
22
+ to <code>true</code>, the generated predicate will use <code>deepEquals</code>,
23
+ requiring the entire object graph to match.</td>
24
+ </tr>
25
+ <tr>
26
+ <td><code>caseSensitive</code></td>
27
+ <td><code>false</code></td>
28
+ <td>boolean</td>
29
+ <td>Determines if the match is case sensitive or not. This includes keys for objects
30
+ such as query parameters.</td>
31
+ </tr>
32
+ <tr>
33
+ <td><code>except</code></td>
34
+ <td><code>""</code></td>
35
+ <td>string</td>
36
+ <td>Defines a regular expression that is stripped out of the request field
37
+ before matching.</td>
38
+ </tr>
39
+ <tr>
40
+ <td><code>xpath</code></td>
41
+ <td><code>null</code></td>
42
+ <td>object</td>
43
+ <td>Defines an object containing a <code>selector</code> string and, optionally, an
44
+ <code>ns</code> object field that defines a namespace map. The predicate's
45
+ scope is limited to the selected value in the request field.</td>
46
+ </tr>
47
+ <tr>
48
+ <td><code>jsonpath</code></td>
49
+ <td><code>null</code></td>
50
+ <td>object</td>
51
+ <td>Defines an object containing a <code>selector</code> string. The predicate's
52
+ scope is limited to the selected value in the request field.</td>
53
+ </tr>
54
+ <tr>
55
+ <td><code>predicateOperator</code></td>
56
+ <td><code>deepEquals</code> or <code>equals</code></td>
57
+ <td>string</td>
58
+ <td>Allows you to override the predicate operator used in the generated predicate.
59
+ This is most often used to substitute an <code>exists</code> operator, e.g., for
60
+ whether the given xpath expression exists in the incoming request or not. At times,
61
+ it may be useful to use a <code>contains</code> operator if future requests can
62
+ add more information to the field.</td>
63
+ </tr>
64
+ <tr>
65
+ <td><code>inject</code></td>
66
+ <td><code>null</code></td>
67
+ <td>string</td>
68
+ <td>Defines a JavaScript function that allows programmatic creation of the predicates.</td>
69
+ </tr>
70
+ <tr>
71
+ <td><code>ignore</code></td>
72
+ <td><code>{}</code></td>
73
+ <td>object</td>
74
+ <td>Use this option to ignore specific key of field from request based on <code>match</code> field.</td>
75
+ </tr>
76
+ </table>
77
+
78
+ <p>With the exception of <code>matches</code> and <code>inject</code>, the fields correspond to the standard
79
+ <a href='/docs/api/predicates'>predicate parameters</a>. Each object in the
80
+ <code>predicateGenerators</code> array generates an object in
81
+ the newly created stub's <code>predicates</code> array. You can decide how strictly
82
+ you want the generated predicates to match object fields. The following example
83
+ matches the root <code>query</code> field in its entirety:</p>
84
+
85
+ <testScenario name='predicate-generators'>
86
+ <step type='http'>
87
+ <code class='hidden'>POST /imposters HTTP/1.1
88
+ Host: localhost:<%= port %>
89
+ Accept: application/json
90
+ Content-Type: application/json
91
+
92
+ {
93
+ "port": 3001,
94
+ "protocol": "http"
95
+ }</code>
96
+ </step>
97
+
98
+ <step type='http'>
99
+ <pre><code><span class='hidden'>POST /imposters HTTP/1.1
100
+ Host: localhost:<%= port %>
101
+ Accept: application/json
102
+ Content-Type: application/json
103
+
104
+ {
105
+ "port": 3000,
106
+ "protocol": "http",
107
+ </span>"stubs": [{
108
+ "responses": [{
109
+ "proxy": {
110
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
111
+ <strong class='highlight1'>"predicateGenerators": [{
112
+ "matches": { "query": true },
113
+ "caseSensitive": true
114
+ }]</strong>
115
+ }
116
+ }]
117
+ }]<span class='hidden'>}</span></code></pre>
118
+ </step>
119
+
120
+ <p>This will generate a <code>deepEquals</code> predicate at the same level, which requires
121
+ that all keys and values in the querystring match (although the order can be different).
122
+ We added the <code>caseSensitive</code> parameter, which will also require the cases of the
123
+ query keys and values to match. If the incoming request is to <code>/test?q=mountebank&page=1</code>,
124
+ the following stub will be generating (the saved response is elided for clarity):</p>
125
+
126
+ <step type='http'>
127
+ <code class='hidden'>GET /test?q=mountebank&page=1 HTTP/1.1
128
+ Host: localhost:3000</code>
129
+ </step>
130
+
131
+ <step type='http'>
132
+ <code class='hidden'>GET /imposters/3000 HTTP/1.1
133
+ Host: localhost:<%= port %></code>
134
+
135
+ <assertResponse partial='true'>
136
+ <pre><code><span class='hidden'>{
137
+ </span>"stubs": [
138
+ {
139
+ <strong class='highlight1'>"predicates": [{
140
+ "caseSensitive": true,
141
+ "deepEquals": {
142
+ "query": {
143
+ "q": "mountebank",
144
+ "page": "1"
145
+ }
146
+ }
147
+ }]</strong>,
148
+ "responses": [{
149
+ "is": { <change to=''><strong class='highlight2'>...</strong> }</change><span class='hidden'>
150
+ "_proxyResponseTime": <volatile>5</volatile>
151
+ }</span>
152
+ }]
153
+ },
154
+ {
155
+ "responses": [{
156
+ "proxy": {
157
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
158
+ "predicateGenerators": [{
159
+ "matches": { "query": true },
160
+ "caseSensitive": true
161
+ }]
162
+ }
163
+ }]
164
+ }
165
+ ]<span class='hidden'>}</span></code></pre>
166
+ </assertResponse>
167
+ </step>
168
+
169
+ <step type='http'>
170
+ <code class='hidden'>DELETE /imposters/3000
171
+ Host: localhost:<%= port %></code>
172
+ </step>
173
+
174
+ <p>Just as with predicates, we could have also specified only the query key we cared about:</p>
175
+
176
+ <step type='http'>
177
+ <pre><code><span class='hidden'>POST /imposters HTTP/1.1
178
+ Host: localhost:<%= port %>
179
+ Accept: application/json
180
+ Content-Type: application/json
181
+
182
+ {
183
+ "port": 3000,
184
+ "protocol": "http",
185
+ </span>"stubs": [{
186
+ "responses": [{
187
+ "proxy": {
188
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
189
+ "predicateGenerators": [{
190
+ "matches": {
191
+ <strong class='highlight1'>"query": { "q": "mountebank" }</strong>
192
+ }
193
+ }]
194
+ }
195
+ }]
196
+ }]<span class='hidden'>}</span></code></pre>
197
+ </step>
198
+
199
+ <p>The same request to <code>/test?q=mountebank&page=1</code> now generates a more
200
+ limited predicate:</p>
201
+
202
+ <step type='http'>
203
+ <code class='hidden'>GET /test?q=mountebank&page=1 HTTP/1.1
204
+ Host: localhost:3000</code>
205
+ </step>
206
+
207
+ <step type='http'>
208
+ <code class='hidden'>GET /imposters/3000 HTTP/1.1
209
+ Host: localhost:<%= port %></code>
210
+
211
+ <assertResponse partial='true'>
212
+ <pre><code><span class='hidden'>{
213
+ </span>"stubs": [
214
+ {
215
+ <strong class='highlight1'>"predicates": [{
216
+ "equals": {
217
+ "query": { "q": "mountebank" }
218
+ }
219
+ }]</strong>,
220
+ "responses": [{
221
+ "is": { <change to=''><strong class='highlight2'>...</strong> }</change><span class='hidden'>
222
+ "_proxyResponseTime": <volatile>5</volatile>
223
+ }</span>
224
+ }]
225
+ },
226
+ {
227
+ "responses": [{
228
+ "proxy": {
229
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
230
+ "predicateGenerators": [{
231
+ "matches": {
232
+ "query": { "q": "mountebank" }
233
+ }
234
+ }]
235
+ }
236
+ }]
237
+ }
238
+ ]<span class='hidden'>}</span></code></pre>
239
+ </assertResponse>
240
+ </step>
241
+
242
+ <step type='http'>
243
+ <code class='hidden'>DELETE /imposters/3000
244
+ Host: localhost:<%= port %></code>
245
+ </step>
246
+
247
+ <h3>xpath</h3>
248
+
249
+ <p>The <a href='/docs/api/xpath'><code>xpath</code></a> and
250
+ <a href='/docs/api/jsonpath'><code>jsonpath</code></a> predicate parameters work
251
+ by limiting the scope of the generated predicate to the matching value in the
252
+ proxied request. If the selector matches multiple values in the proxied request,
253
+ they will all be part of the generated predicate, using standard predicate
254
+ <a href='/docs/api/predicates#array-match'>array matching rules</a>. For example,
255
+ following stub will look for <code>number</code> tags in the XML body:</p>
256
+
257
+ <step type='http'>
258
+ <pre><code><span class='hidden'>POST /imposters HTTP/1.1
259
+ Host: localhost:<%= port %>
260
+ Accept: application/json
261
+ Content-Type: application/json
262
+
263
+ {
264
+ "port": 3000,
265
+ "protocol": "http",
266
+ </span>"stubs": [{
267
+ "responses": [{
268
+ "proxy": {
269
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
270
+ <strong class='highlight1'>"predicateGenerators": [{
271
+ "matches": { "body": true },
272
+ "xpath": { "selector": "//number" }
273
+ }]</strong>
274
+ }
275
+ }]
276
+ }]<span class='hidden'>}</span></code></pre>
277
+ </step>
278
+
279
+ <p>We'll send it the following XML body:</p>
280
+
281
+ <step type='http'>
282
+ <pre><code><span class='hidden'>POST / HTTP/1.1
283
+ Host: localhost:3000
284
+
285
+ </span>&lt;doc&gt;
286
+ &lt;number&gt;1&lt;/number&gt;
287
+ &lt;number&gt;2&lt;/number&gt;
288
+ &lt;number&gt;3&lt;/number&gt;
289
+ &lt;/doc&gt;</code></pre>
290
+ </step>
291
+
292
+ <p>Rather than matching the entire body, the generated predicate will require all three
293
+ values matching the xpath selector in the original proxied request to be present (in any
294
+ order):</p>
295
+
296
+ <step type='http'>
297
+ <code class='hidden'>GET /imposters/3000 HTTP/1.1
298
+ Host: localhost:<%= port %></code>
299
+
300
+ <assertResponse partial='true'>
301
+ <pre><code><span class='hidden'>{
302
+ </span>"stubs": [
303
+ {
304
+ <strong class='highlight1'>"predicates": [{
305
+ "xpath": { "selector": "//number" },
306
+ "deepEquals": { "body": ["1", "2", "3"] }
307
+ }]</strong>,
308
+ "responses": [{
309
+ "is": { <change to='"statusCode": 200'><strong class='highlight2'>...</strong></change> }
310
+ }]
311
+ },
312
+ {
313
+ "responses": [{
314
+ "proxy": {
315
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
316
+ "predicateGenerators": [{
317
+ "matches": { "body": true },
318
+ "xpath": { "selector": "//number" }
319
+ }]
320
+ }
321
+ }]
322
+ }
323
+ ]<span class='hidden'>}</span></code></pre>
324
+ </assertResponse>
325
+ </step>
326
+
327
+ <step type='http'>
328
+ <code class='hidden'>DELETE /imposters/3000
329
+ Host: localhost:<%= port %></code>
330
+ </step>
331
+
332
+ <h3>jsonpath</h3>
333
+
334
+ <p>Similarly, the following stub looks for number elements in JSON:</p>
335
+
336
+ <step type='http'>
337
+ <pre><code><span class='hidden'>POST /imposters HTTP/1.1
338
+ Host: localhost:<%= port %>
339
+ Accept: application/json
340
+ Content-Type: application/json
341
+
342
+ {
343
+ "port": 3000,
344
+ "protocol": "http",
345
+ </span>"stubs": [{
346
+ "responses": [{
347
+ "proxy": {
348
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
349
+ <strong class='highlight1'>"predicateGenerators": [{
350
+ "matches": { "body": true },
351
+ "jsonpath": { "selector": "$..number" }
352
+ }]</strong>
353
+ }
354
+ }]
355
+ }]<span class='hidden'>}</span></code></pre>
356
+ </step>
357
+
358
+ <p>We'll send it the following JSON body:</p>
359
+
360
+ <step type='http'>
361
+ <pre><code><span class='hidden'>POST / HTTP/1.1
362
+ Host: localhost:3000
363
+
364
+ </span>[
365
+ { "number": 1 },
366
+ { "number": 2 },
367
+ { "number": 3 }
368
+ ]</code></pre>
369
+ </step>
370
+
371
+ <p>Again, the generated predicate gets scoped to the jsonpath matches.</p>
372
+
373
+ <step type='http'>
374
+ <code class='hidden'>GET /imposters/3000 HTTP/1.1
375
+ Host: localhost:<%= port %></code>
376
+
377
+ <assertResponse partial='true'>
378
+ <pre><code><span class='hidden'>{
379
+ </span>"stubs": [
380
+ {
381
+ <strong class='highlight1'>"predicates": [{
382
+ "jsonpath": { "selector": "$..number" },
383
+ "deepEquals": { "body": [1, 2, 3] }
384
+ }]</strong>,
385
+ "responses": [{
386
+ "is": { <change to='"statusCode": 200'><strong class='highlight2'>...</strong></change> }
387
+ }]
388
+ },
389
+ {
390
+ "responses": [{
391
+ "proxy": {
392
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
393
+ "predicateGenerators": [{
394
+ "matches": { "body": true },
395
+ "jsonpath": { "selector": "$..number" }
396
+ }]
397
+ }
398
+ }]
399
+ }
400
+ ]<span class='hidden'>}</span></code></pre>
401
+ </assertResponse>
402
+ </step>
403
+
404
+ <step type='http'>
405
+ <code class='hidden'>DELETE /imposters/3000
406
+ Host: localhost:<%= port %></code>
407
+ </step>
408
+
409
+ <h3>inject</h3>
410
+
411
+ <p>In advanced scenarios, you need more fine-grained control over the creation of the predicates.
412
+ In such scenarios, you can use the <code>inject</code> option.</p>
413
+
414
+ <p class='warning-icon'>The <code>inject</code> option requires the <code>--allowInjection</code>
415
+ command line option.</p>
416
+
417
+ <p>The <code>inject</code> field takes a string representing a JavaScript function that is expected
418
+ to return an array of predicate objects. Since it provides full control over the predicate generation,
419
+ the <code>inject</code> option will ignore any other parameters, like <code>xpath</code>. The function
420
+ accepts a single object parameter, which contains the following fields:</p>
421
+
422
+ <table>
423
+ <tr>
424
+ <th>Field</th>
425
+ <th>Description</th>
426
+ </tr>
427
+ <tr>
428
+ <td><code>request</code></td>
429
+ <td>The entire request object, containing all request fields</td>
430
+ </tr>
431
+ <tr>
432
+ <td><code>logger</code></td>
433
+ <td>A logger object with <code>debug</code>, <code>info</code>, <code>warn</code>,
434
+ and <code>error</code> functions to write to the mountebank logs.</td>
435
+ </tr>
436
+ </table>
437
+
438
+ <p>In this example, we'll add a predicate if a specific header exists. Here's the injection
439
+ function fully expanded:</p>
440
+
441
+ <pre><code>
442
+ function (config) {
443
+ const predicate = { exists: { headers: { 'X-Transaction-Id': false } } };
444
+ if (config.request.headers['X-Transaction-Id']) {
445
+ config.logger.debug('Requiring X-Transaction-Id header to exist in predicate');
446
+ predicate.exists.headers['X-Transaction-Id'] = true;
447
+ }
448
+ return [predicate];
449
+ }
450
+ </code></pre>
451
+
452
+ <p>First let's add the imposter. The function must be passed as a single string, which makes it largely
453
+ unreadable inline.</p>
454
+
455
+ <step type='http'>
456
+ <pre><code><span class='hidden'>POST /imposters HTTP/1.1
457
+ Host: localhost:<%= port %>
458
+ Accept: application/json
459
+ Content-Type: application/json
460
+
461
+ {
462
+ "port": 3000,
463
+ "protocol": "http",
464
+ </span>"stubs": [{
465
+ "responses": [{
466
+ "proxy": {
467
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
468
+ <strong class='highlight1'>"predicateGenerators": [{
469
+ "inject": "function (config) {\n const predicate = { exists: { headers: { 'X-Transaction-Id': false } } };\n if (config.request.headers['X-Transaction-Id']) {\n config.logger.debug('Requiring X-Transaction-Id header to exist in predicate');\n predicate.exists.headers['X-Transaction-Id'] = true;\n }\n return [predicate];\n}"
470
+ }]</strong>
471
+ }
472
+ }]
473
+ }]<span class='hidden'>}</span></code></pre>
474
+ </step>
475
+
476
+ <p>We'll send it the following HTTP request:</p>
477
+
478
+ <step type='http'>
479
+ <pre><code>POST / HTTP/1.1
480
+ Host: localhost:3000
481
+ <strong class='highlight1'>X-Transaction-Id: 100</strong>
482
+
483
+ SUCCESS</code></pre>
484
+ </step>
485
+
486
+ <p>Since the <code>X-Transaction-Id</code> header was passed, the generated predicate requires
487
+ it to exist to replay the response later.</p>
488
+
489
+ <step type='http'>
490
+ <code class='hidden'>GET /imposters/3000 HTTP/1.1
491
+ Host: localhost:<%= port %></code>
492
+
493
+ <assertResponse partial='true'>
494
+ <pre><code><span class='hidden'>{
495
+ </span>"stubs": [
496
+ {
497
+ <strong class='highlight1'>"predicates": [{
498
+ "exists": { "headers": { "X-Transaction-Id": true } }
499
+ }]</strong>,
500
+ "responses": [{
501
+ "is": { <change to='"statusCode": 200'><strong class='highlight2'>...</strong></change> }
502
+ }]
503
+ },
504
+ {
505
+ "responses": [{
506
+ "proxy": {
507
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
508
+ "predicateGenerators": [{
509
+ "inject": "function (config) {\n const predicate = { exists: { headers: { 'X-Transaction-Id': false } } };\n if (config.request.headers['X-Transaction-Id']) {\n config.logger.debug('Requiring X-Transaction-Id header to exist in predicate');\n predicate.exists.headers['X-Transaction-Id'] = true;\n }\n return [predicate];\n}"
510
+ }]
511
+ }
512
+ }]
513
+ }
514
+ ]<span class='hidden'>}</span></code></pre>
515
+ </assertResponse>
516
+ </step>
517
+
518
+ <step type='http'>
519
+ <code class='hidden'>DELETE /imposters/3000
520
+ Host: localhost:<%= port %></code>
521
+ </step>
522
+
523
+ <h3>ignore</h3>
524
+
525
+ <p>Support ignoring certain keys in predicateGenerators</p>
526
+
527
+ <step type='http'>
528
+ <pre><code><span class='hidden'>POST /imposters HTTP/1.1
529
+ Host: localhost:<%= port %>
530
+ Accept: application/json
531
+ Content-Type: application/json
532
+
533
+ {
534
+ "port": 3000,
535
+ "protocol": "http",
536
+ </span>"stubs": [
537
+ {
538
+ "responses": [{
539
+ "proxy": {
540
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
541
+ <strong class='highlight1'>"predicateGenerators": [{
542
+ "matches": { "query": true },
543
+ "ignore": { "query": "startDate" }
544
+ }]</strong>
545
+ }
546
+ }]
547
+ }
548
+ ]<span class='hidden'>}</span></code></pre>
549
+ </step>
550
+
551
+ <p>Then we get a request <code>/path?limit=100&enhanced=true&startDate=2017-09-07&endDate=2017-10-11</code></p>
552
+
553
+ <step type='http'>
554
+ <code class='hidden'>GET /path?limit=100&enhanced=true&startDate=2017-09-07&endDate=2017-10-11 HTTP/1.1
555
+ Host: localhost:3000
556
+ </code>
557
+ </step>
558
+
559
+ <p>The saved predicate should not have the <code>startDate</code> key.</p>
560
+
561
+ <step type='http'>
562
+ <code class='hidden'>GET /imposters/3000 HTTP/1.1
563
+ Host: localhost:<%= port %></code>
564
+
565
+ <assertResponse partial='true'>
566
+ <pre><code><span class='hidden'>{
567
+ </span>"stubs": [
568
+ {
569
+ <strong class='highlight1'>"predicates": [{
570
+ "deepEquals": { "query": { "limit": "100", "enhanced": "true", "endDate": "2017-10-11" } }
571
+ }]</strong>,
572
+ "responses": [{
573
+ "is": { <change to='"statusCode": 200'><strong class='highlight2'>...</strong></change> }
574
+ }]
575
+ },
576
+ {
577
+ "responses": [{
578
+ "proxy": {
579
+ "to": "<change to='http://localhost:3001'>http://origin-server.com</change>",
580
+ "predicateGenerators": [{
581
+ "matches": { "query": true },
582
+ "ignore": { "query": "startDate" }
583
+ }]
584
+ }
585
+ }]
586
+ }
587
+ ]<span class='hidden'>}</span></code></pre>
588
+ </assertResponse>
589
+ </step>
590
+
591
+ <step type='http'>
592
+ <code class='hidden'>DELETE /imposters/3000
593
+ Host: localhost:<%= port %></code>
594
+ </step>
595
+
596
+ <step type='http'>
597
+ <code class='hidden'>DELETE /imposters/3001
598
+ Host: localhost:<%= port %></code>
599
+ </step>
600
+ </testScenario>