@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,427 @@
1
+ <table>
2
+ <tr>
3
+ <th>Parameter</th>
4
+ <th>Type</th>
5
+ <th>Description</th>
6
+ </tr>
7
+ <tr>
8
+ <td><code>copy</code></td>
9
+ <td>An object</td>
10
+ <td>An object specifying the request field and response token, as well as a way
11
+ of selecting the value from the request field</td>
12
+ </tr>
13
+ <tr>
14
+ <td><code>copy.from</code></td>
15
+ <td>A string or an object</td>
16
+ <td>The name of the request field to copy from, or, if the request field is an object,
17
+ then an object specifying the path to the request field. For example,
18
+ <pre><code>{ "from": "body" }</code></pre> and <pre><code>{ "from": { "query": "q" } }</code></pre>
19
+ are both valid.</td>
20
+ </tr>
21
+ <tr>
22
+ <td><code>copy.into</code></td>
23
+ <td>A string</td>
24
+ <td>The token to replace in the response with the selected request value. There is
25
+ no need to specify which field in the response the token will be in; all response
26
+ tokens will be replaced in all response fields. Sometimes, the request selection
27
+ returns multiple values. In those cases, you can add an index to the token, while
28
+ the unindexed token represents the first match. For example, if you specify
29
+ <pre><code>{ "into": "${NAME}" }</code></pre> as your token configuration, then both
30
+ <code>${NAME}</code> and <code>${NAME}[0]</code> will be replaced by the first match,
31
+ <code>${NAME}[1]</code> will be replaced by the second match, and so on.</td>
32
+ </tr>
33
+ <tr>
34
+ <td><code>copy.using</code></td>
35
+ <td>An object</td>
36
+ <td>The configuration needed to select values from the response</td>
37
+ </tr>
38
+ <tr>
39
+ <td><code>copy.using.method</code></td>
40
+ <td>An string</td>
41
+ <td>The method used to select the value(s) from the request. Allowed values are <code>regex</code>,
42
+ <code>xpath</code>, and <code>jsonpath</code>.</td>
43
+ </tr>
44
+ <tr>
45
+ <td><code>copy.using.selector</code></td>
46
+ <td>An string</td>
47
+ <td>The selector used to select the value(s) from the request. For a <code>regex</code>, this would
48
+ be the pattern, and the replacement value will be the entire match. Match groups using parentheses are supported
49
+ and can be replaced using indexed tokens as described in the <code>copy[].into</code> description.
50
+ <code>xpath</code> and <code>jsonpath</code> selectors work on XML and JSON documents. If the request
51
+ value does not match the selector (including through XML or JSON parsing errors), nothing is replaced.</td>
52
+ </tr>
53
+ <tr>
54
+ <td><code>copy.using.ns</code></td>
55
+ <td>An object</td>
56
+ <td>For <code>xpath</code> selectors, the <code>ns</code> object maps namespace aliases to URLs</td>
57
+ </tr>
58
+ <tr>
59
+ <td><code>copy.using.options</code></td>
60
+ <td>An object</td>
61
+ <td>For <code>regex</code> selectors, the <code>options</code> object describes the regular expression options</td>
62
+ </tr>
63
+ <tr>
64
+ <td><code>copy.using.options.ignoreCase</code></td>
65
+ <td>A boolean</td>
66
+ <td>Uses a <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase'>case-insensitive</a>
67
+ regular expression</td>
68
+ </tr>
69
+ <tr>
70
+ <td><code>copy.using.options.multiline</code></td>
71
+ <td>A boolean</td>
72
+ <td>Uses a <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline'>multiline</a>
73
+ regular expression</td>
74
+ </tr>
75
+ </table>
76
+
77
+ <p>The <code>copy</code> behavior supports dynamically replacing values in the response with something that
78
+ comes from the request. It relies on you adding tokens of your own choosing into the response fields you want
79
+ replaced. We'll look at the following examples:</p>
80
+
81
+ <ul class='bullet-list'>
82
+ <li><a href='#copy-regex-replacement'>Regular expressions</a></li>
83
+ <li><a href='#copy-xpath-replacement'>xpath</a></li>
84
+ <li><a href='#copy-jsonpath-replacement'>jsonpath</a></li>
85
+ <li><a href='#copy-indexed-replacement'>Using match groups and indexed replacements</a></li>
86
+ </ul>
87
+
88
+ <h3 id='copy-regex-replacement'>Regular expressions</h3>
89
+
90
+ <p>The following example shows multiple regular expression matches on request fields to copy into the response.</p>
91
+
92
+ <testScenario name='copy-regex'>
93
+ <step type='http'>
94
+ <pre><code>POST /imposters HTTP/1.1
95
+ Host: localhost:<%= port %>
96
+ Accept: application/json
97
+ Content-Type: application/json
98
+
99
+ {
100
+ "port": 8585,
101
+ "protocol": "http",
102
+ "stubs": [
103
+ {
104
+ "responses": [
105
+ {
106
+ "is": {
107
+ "statusCode": "<strong class='highlight1'>${code}</strong>",
108
+ "headers": {
109
+ "X-Test": "<strong class='highlight2'>${header}</strong>"
110
+ },
111
+ "body": "The request name was <strong class='highlight3'>${name}</strong>. Hello, <strong class='highlight3'>${name}</strong>!"
112
+ },
113
+ "behaviors": [
114
+ {
115
+ "copy": {
116
+ "from": "path",
117
+ "into": "<strong class='highlight1'>${code}</strong>",
118
+ "using": { "method": "regex", "selector": "\\d+" }
119
+ }
120
+ },
121
+ {
122
+ "copy": {
123
+ "from": { "headers": "X-Request" },
124
+ "into": "<strong class='highlight2'>${header}</strong>",
125
+ "using": { "method": "regex", "selector": ".+" }
126
+ }
127
+ },
128
+ {
129
+ "copy": {
130
+ "from": { "query": "name" },
131
+ "into": "<strong class='highlight3'>${name}</strong>",
132
+ "using": {
133
+ "method": "regex",
134
+ "selector": "MOUNT\\w+$",
135
+ "options": { "ignoreCase": true }
136
+ }
137
+ }
138
+ }
139
+ ]
140
+ }
141
+ ]
142
+ }
143
+ ]
144
+ }</code></pre>
145
+ </step>
146
+
147
+ <p>This example shows off many of the options of the <code>copy</code> behavior. For example,
148
+ we can plug tokens into any of the response fields (including the <code>statusCode</code>), and
149
+ it shows how to navigate object request fields, like the <code>name</code> querystring
150
+ parameter. It shows an example of using regular expressions options to get a case-insensitive
151
+ regular expression to capture the <code>name</code> query parameter. It also shows matching multiple
152
+ request fields using an array of <code>copy</code> configurations. Let's see what happens when we
153
+ craft a request to match all of those selectors:</p>
154
+
155
+ <step type='http'>
156
+ <pre><code>GET /statusCode/<strong class='highlight1'>400</strong>?ignore=this&<strong class='highlight3'>name=mountebank</strong> HTTP/1.1
157
+ Host: localhost:8585
158
+ X-REQUEST: <strong class='highlight2'>Header value</strong></code></pre>
159
+
160
+ <assertResponse>
161
+ <pre><code>HTTP/1.1 <strong class='highlight1'>400</strong> Bad Request
162
+ X-Test: <strong class='highlight2'>Header value</strong>
163
+ Connection: close
164
+ Date: <volatile>Thu, 28 Dec 2016 11:37:31 GMT</volatile>
165
+ Transfer-Encoding: chunked
166
+
167
+ The request name was <strong class='highlight3'>mountebank</strong>. Hello, <strong class='highlight3'>mountebank</strong>!</code></pre>
168
+ </assertResponse>
169
+ </step>
170
+
171
+ <step type='http'>
172
+ <code class='hidden'>DELETE /imposters/8585 HTTP/1.1
173
+ Host: localhost:<%= port %></code>
174
+ </step>
175
+ </testScenario>
176
+
177
+ <h3 id='copy-xpath-replacement'>xpath</h3>
178
+
179
+ <p>The following example shows a simple namespaced xpath match to grab the first <code>title</code> field in
180
+ an XML document and copy it into the <code>BOOK</code> response token.</p>
181
+
182
+ <testScenario name='copy-xpath'>
183
+ <step type='http'>
184
+ <pre><code>POST /imposters HTTP/1.1
185
+ Host: localhost:<%= port %>
186
+ Accept: application/json
187
+ Content-Type: application/json
188
+
189
+ {
190
+ "port": 8586,
191
+ "protocol": "http",
192
+ "stubs": [
193
+ {
194
+ "responses": [
195
+ {
196
+ "is": {
197
+ "body": "Have you read <strong class='highlight1'>BOOK</strong>?"
198
+ },
199
+ "behaviors": [
200
+ {
201
+ "copy": {
202
+ "from": "body",
203
+ "into": "<strong class='highlight1'>BOOK</strong>",
204
+ "using": {
205
+ "method": "xpath",
206
+ "selector": "//isbn:title",
207
+ "ns": {
208
+ "isbn": "http://schemas.isbn.org/ns/1999/basic.dtd"
209
+ }
210
+ }
211
+ }
212
+ }
213
+ ]
214
+ }
215
+ ]
216
+ }
217
+ ]
218
+ }</code></pre>
219
+ </step>
220
+
221
+ <p>The <code>ns</code> object map is optional and can be ignored if your xpath selector doesn't depend on
222
+ namespaces. It doesn't matter how many <code>name</code> elements exist in the XML. Without using indexed
223
+ tokens, only the first match will be used:</p>
224
+
225
+ <step type='http'>
226
+ <pre><code>POST /names HTTP/1.1
227
+ Host: localhost:8586
228
+
229
+ &lt;books xmlns:isbn=&quot;http://schemas.isbn.org/ns/1999/basic.dtd&quot;&gt;
230
+ &lt;book&gt;
231
+ &lt;isbn:title&gt;<strong class='highlight1'>Game of Thrones</strong>&lt;/isbn:title&gt;
232
+ &lt;isbn:summary&gt;Dragons and political intrigue&lt;/isbn:summary&gt;
233
+ &lt;/book&gt;
234
+ &lt;book&gt;
235
+ &lt;isbn:title&gt;Harry Potter&lt;/isbn:title&gt;
236
+ &lt;isbn:summary&gt;Dragons and a boy wizard&lt;/isbn:summary&gt;
237
+ &lt;/book&gt;
238
+ &lt;book&gt;
239
+ &lt;isbn:title&gt;The Hobbit&lt;/isbn:title&gt;
240
+ &lt;isbn:summary&gt;A dragon and short people&lt;/isbn:summary&gt;
241
+ &lt;/book&gt;
242
+ &lt;/books&gt;</code></pre>
243
+
244
+ <assertResponse>
245
+ <pre><code>HTTP/1.1 200 OK
246
+ Connection: close
247
+ Date: <volatile>Thu, 28 Dec 2016 11:37:31 GMT</volatile>
248
+ Transfer-Encoding: chunked
249
+
250
+ Have you read <strong class='highlight1'>Game of Thrones</strong>?</code></pre>
251
+ </assertResponse>
252
+ </step>
253
+
254
+ <step type='http'>
255
+ <code class='hidden'>DELETE /imposters/8586 HTTP/1.1
256
+ Host: localhost:<%= port %></code>
257
+ </step>
258
+ </testScenario>
259
+
260
+ <h3 id='copy-jsonpath-replacement'>jsonpath</h3>
261
+
262
+ <p>The following example translates the XML example above into JSON. To make it more interesting,
263
+ we'll show it using the <code>tcp</code> protocol</p>
264
+
265
+ <testScenario name='copy-jsonpath'>
266
+ <step type='http'>
267
+ <pre><code>POST /imposters HTTP/1.1
268
+ Host: localhost:<%= port %>
269
+ Accept: application/json
270
+ Content-Type: application/json
271
+
272
+ {
273
+ "port": 8587,
274
+ "protocol": "tcp",
275
+ "stubs": [
276
+ {
277
+ "responses": [
278
+ {
279
+ "is": {
280
+ "data": "Have you read <strong class='highlight1'>BOOK</strong>?"
281
+ },
282
+ "behaviors": [
283
+ {
284
+ "copy": {
285
+ "from": "data",
286
+ "into": "<strong class='highlight1'>BOOK</strong>",
287
+ "using": {
288
+ "method": "jsonpath",
289
+ "selector": "$..title"
290
+ }
291
+ }
292
+ }
293
+ ]
294
+ }
295
+ ]
296
+ }
297
+ ]
298
+ }</code></pre>
299
+ </step>
300
+
301
+ <p>Again, by default only the first match will be used:</p>
302
+
303
+ <step type='exec'>
304
+ <pre><code>echo '{
305
+ "books": [
306
+ {
307
+ "title": "<strong class='highlight1'>Game of Thrones</strong>",
308
+ "summary": "Dragons and political intrigue"
309
+ },
310
+ {
311
+ "title": "Harry Potter",
312
+ "summary": "Dragons and a boy wizard"
313
+ },
314
+ {
315
+ "title": "The Hobbit",
316
+ "summary": "A dragon and short people"
317
+ }
318
+ ]
319
+ }' | nc localhost 8587</code></pre>
320
+
321
+ <assertResponse>
322
+ <pre><code>Have you read <strong class='highlight1'>Game of Thrones</strong>?</code></pre>
323
+ </assertResponse>
324
+ </step>
325
+
326
+ <step type='http'>
327
+ <code>DELETE /imposters/8587 HTTP/1.1
328
+ Host: localhost:<%= port %></code>
329
+ </step>
330
+ </testScenario>
331
+
332
+ <h3 id='copy-indexed-replacement'>Indexed replacements</h3>
333
+
334
+ <p>Finally, let's show an example that uses multiple matches for a given selector. To
335
+ show that the same approach works for multiple selection methods, we'll show it for both
336
+ regular expressions and jsonpath:</p>
337
+
338
+ <testScenario name='copy-indexed'>
339
+ <step type='http'>
340
+ <pre><code>POST /imposters HTTP/1.1
341
+ Host: localhost:<%= port %>
342
+ Accept: application/json
343
+ Content-Type: application/json
344
+
345
+ {
346
+ "port": 8588,
347
+ "protocol": "http",
348
+ "stubs": [
349
+ {
350
+ "responses": [
351
+ {
352
+ "is": {
353
+ "body": "<strong class='highlight2'>${BOOK}[1]</strong>: <strong class='highlight1'>${SUMMARY}[0]</strong>\n<strong class='highlight2'>${BOOK}[2]</strong>: <strong class='highlight1'>${SUMMARY}[1]</strong>\n<strong class='highlight2'>${BOOK}[3]</strong>: <strong class='highlight1'>${SUMMARY}[2]</strong>"
354
+ },
355
+ "behaviors": [
356
+ {
357
+ "copy": {
358
+ "from": "body",
359
+ "into": "<strong class='highlight1'>${SUMMARY}</strong>",
360
+ "using": {
361
+ "method": "jsonpath",
362
+ "selector": "$..summary"
363
+ }
364
+ }
365
+ },
366
+ {
367
+ "copy": {
368
+ "from": { "query": "books" },
369
+ "into": "<strong class='highlight2'>${BOOK}</strong>",
370
+ "using": {
371
+ "method": "regex",
372
+ "selector": "([^,]+),([^,]+),(.+)$"
373
+ }
374
+ }
375
+ }
376
+ ]
377
+ }
378
+ ]
379
+ }
380
+ ]
381
+ }</code></pre>
382
+ </step>
383
+
384
+ <p>Note the mismatched indexes between the two selection methods. This is because we use the standard regular
385
+ expression semantics around matched groups, which is that the first element in the matches will be the entire
386
+ matched expression, the second element will be the first parenthesized match group, and so on. Also note that
387
+ <code>${SUMMARY}[0]</code> and <code>${SUMMARY}</code> will be treated identically. We'll
388
+ trigger the substitutions with the following request:</p>
389
+
390
+ <step type='http'>
391
+ <pre><code>POST /?books=<strong class='highlight2'>Game%20of%20Thrones</strong>,<strong class='highlight2'>Harry%20Potter</strong>,<strong class='highlight2'>The%20Hobbit</strong> HTTP/1.1
392
+ Host: localhost:8588
393
+
394
+ {
395
+ "books": [
396
+ {
397
+ "title": "Game of Thrones",
398
+ "summary": "<strong class='highlight1'>Dragons and political intrigue</strong>"
399
+ },
400
+ {
401
+ "title": "Harry Potter",
402
+ "summary": "<strong class='highlight1'>Dragons and a boy wizard</strong>"
403
+ },
404
+ {
405
+ "title": "The Hobbit",
406
+ "summary": "<strong class='highlight1'>A dragon and short people</strong>"
407
+ }
408
+ ]
409
+ }</code></pre>
410
+
411
+ <assertResponse>
412
+ <pre><code>HTTP/1.1 200 OK
413
+ Connection: close
414
+ Date: <volatile>Thu, 28 Dec 2016 11:37:31 GMT</volatile>
415
+ Transfer-Encoding: chunked
416
+
417
+ <strong class='highlight2'>Game of Thrones</strong>: <strong class='highlight1'>Dragons and political intrigue</strong>
418
+ <strong class='highlight2'>Harry Potter</strong>: <strong class='highlight1'>Dragons and a boy wizard</strong>
419
+ <strong class='highlight2'>The Hobbit</strong>: <strong class='highlight1'>A dragon and short people</strong></code></pre>
420
+ </assertResponse>
421
+ </step>
422
+
423
+ <step type='http'>
424
+ <code class='hidden'>DELETE /imposters/8588 HTTP/1.1
425
+ Host: localhost:<%= port %></code>
426
+ </step>
427
+ </testScenario>
@@ -0,0 +1,182 @@
1
+ <table>
2
+ <tr>
3
+ <th>Parameter</th>
4
+ <th>Type</th>
5
+ <th>Description</th>
6
+ </tr>
7
+ <tr>
8
+ <td><code>decorate</code></td>
9
+ <td>A string</td>
10
+ <td>The <code>decorate</code> function, used to transform the response through JavaScript. It can either
11
+ mutate the response in place or return a new response object.
12
+
13
+ <p class='warning-icon'>The <code><a href='/docs/commandLine'>--allowInjection</a></code> command
14
+ line flag must be set to support this behavior.</p>
15
+ </td>
16
+ </tr>
17
+ </table>
18
+
19
+ <p>The <code>decorate</code> function should take a single parameter, which will contain the following fields:</p>
20
+
21
+ <table>
22
+ <tr>
23
+ <th>Field</th>
24
+ <th>Description</th>
25
+ </tr>
26
+ <tr>
27
+ <td><code>request</code></td>
28
+ <td>The entire request object, containing all request fields</td>
29
+ </tr>
30
+ <tr>
31
+ <td><code>response</code></td>
32
+ <td>The entire response object</td>
33
+ </tr>
34
+ <tr>
35
+ <td><code>state</code></td>
36
+ <td>An initially empty object, scoped to the imposter, that will be shared with
37
+ <a href='/docs/api/injection#predicate-injection'>predicate</a> and
38
+ <a href='/docs/api/injection#response-injection'>response injection</a> functions.
39
+ You can use it to capture and mutate shared state.</td>
40
+ </tr>
41
+ <tr>
42
+ <td><code>logger</code></td>
43
+ <td>A logger object with <code>debug</code>, <code>info</code>, <code>warn</code>,
44
+ and <code>error</code> functions to write to the mountebank logs.</td>
45
+ </tr>
46
+ </table>
47
+
48
+ <p>The <code>decorate</code> behavior is quite powerful, allowing nearly unlimited (synchronous) post-processing
49
+ of the response. Since it relies on JavaScript injection, the <a href='/docs/commandLine'>--allowInjection</a>
50
+ flag must be passed in to <code>mb</code> on startup.</p>
51
+
52
+ <p>Here are a couple ideas of what to do with post-processing:</p>
53
+
54
+ <ul class='bullet-list'>
55
+ <li><a href='#add-timestamp'>Add the current time to a response</a></li>
56
+ <li><a href='#add-custom-header'>Add a custom header to a proxied response</a></li>
57
+ </ul>
58
+
59
+ <h3 id='add-timestamp'>Add the current time to a response</h3>
60
+
61
+ <p>Many people store static imposter files and load them via the <a href='/docs/commandLine'>--configfile</a>
62
+ command line switch. The <code>decorate</code> behavior supports an elegant way of adding dynamic data
63
+ to the responses. Let's add the current timestamp to each response. We'll pass in a stringified version
64
+ of the following JavaScript function:</p>
65
+
66
+ <testScenario name='decorate'>
67
+ <pre><code>(config) => {
68
+ var pad = function (number) {
69
+ return (number &lt; 10) ? '0' + number : number.toString();
70
+ },
71
+ now = new Date(),
72
+ time = pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds());
73
+
74
+ config.response.body = config.response.body.replace('${TIME}', time);
75
+ }</code></pre>
76
+
77
+ <step type='http'>
78
+ <pre><code>POST /imposters HTTP/1.1
79
+ Host: localhost:<%= port %>
80
+ Accept: application/json
81
+ Content-Type: application/json
82
+
83
+ {
84
+ "port": 5545,
85
+ "protocol": "http",
86
+ "stubs": [
87
+ {
88
+ "responses": [
89
+ {
90
+ "is": {
91
+ "body": "The time is <strong class='highlight1'>${TIME}</strong>"
92
+ },
93
+ "behaviors": [
94
+ { "decorate": "(config) => { var pad = function (number) { return (number &lt; 10) ? '0' + number : number.toString(); }, now = new Date(), time = pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds()); config.response.body = config.response.body.replace('${TIME}', time); }" }
95
+ ]
96
+ }
97
+ ]
98
+ }
99
+ ]
100
+ }</code></pre>
101
+ </step>
102
+
103
+ <p>Now we can call the imposter to get the current time.</p>
104
+
105
+ <step type='http'>
106
+ <pre><code>GET / HTTP/1.1
107
+ Host: localhost:5545</code></pre>
108
+
109
+ <assertResponse>
110
+ <pre><code>HTTP/1.1 200 OK
111
+ Connection: close
112
+ Date: <volatile>Thu, 01 Jan 2015 02:30:31 GMT</volatile>
113
+ Transfer-Encoding: chunked
114
+
115
+ The time is <volatile><strong class='highlight1'>16:43:02</strong></volatile></code></pre>
116
+ </assertResponse>
117
+ </step>
118
+
119
+ <h3 id='add-custom-header'>Add a custom header to a proxied response</h3>
120
+
121
+ <p><a href='/docs/api/proxies'>Proxying</a> provides considerable power out of the box. However,
122
+ there are times where you need to augment the proxied response with something else to properly
123
+ simulate your testing scenario. In this example, we'll proxy to the
124
+ <a href='#add-timestamp'>example above</a> (port 5545) that adds the current time to the response body.
125
+ We'll augment the proxied response with a custom header. Here's the decorator function, passed
126
+ into the imposter creation as a string:</p>
127
+
128
+ <pre><code>(config) => {
129
+ config.response.headers['X-Test'] = 'True';
130
+ }</code></pre>
131
+
132
+ <step type='http'>
133
+ <pre><code>POST /imposters HTTP/1.1
134
+ Host: localhost:<%= port %>
135
+ Accept: application/json
136
+ Content-Type: application/json
137
+
138
+ {
139
+ "port": 7545,
140
+ "protocol": "http",
141
+ "stubs": [
142
+ {
143
+ "responses": [
144
+ {
145
+ "proxy": { "to": "http://localhost:5545" },
146
+ "behaviors": [
147
+ { "decorate": "(config) => { <strong class='highlight1'>config.response.headers['X-Test'] = 'True';</strong> }" }
148
+ ]
149
+ }
150
+ ]
151
+ }
152
+ ]
153
+ }</code></pre>
154
+ </step>
155
+
156
+ <p>Now we should get the custom header back:</p>
157
+
158
+ <step type='http'>
159
+ <pre><code>GET /test HTTP/1.1
160
+ Host: localhost:7545</code></pre>
161
+
162
+ <assertResponse>
163
+ <pre><code>HTTP/1.1 200 OK
164
+ Connection: close
165
+ Date: <volatile>Thu, 01 Jan 2015 02:30:31 GMT</volatile>
166
+ Transfer-Encoding: chunked
167
+ <strong class='highlight1'>X-Test: True</strong>
168
+
169
+ The time is <volatile>17:16:23</volatile></code></pre>
170
+ </assertResponse>
171
+ </step>
172
+
173
+ <step type='http'>
174
+ <code class='hidden'>DELETE /imposters/5545 HTTP/1.1
175
+ Host: localhost:<%= port %></code>
176
+ </step>
177
+
178
+ <step type='http'>
179
+ <code class='hidden'>DELETE /imposters/7545 HTTP/1.1
180
+ Host: localhost:<%= port %></code>
181
+ </step>
182
+ </testScenario>