@mbtest/mountebank 2.9.2-beta.9050
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/bin/mb +136 -0
- package/package.json +71 -0
- package/releases.json +52 -0
- package/src/cli/api.js +112 -0
- package/src/cli/cli.js +420 -0
- package/src/controllers/configController.js +64 -0
- package/src/controllers/feedController.js +115 -0
- package/src/controllers/homeController.js +58 -0
- package/src/controllers/imposterController.js +328 -0
- package/src/controllers/impostersController.js +215 -0
- package/src/controllers/logsController.js +52 -0
- package/src/models/behaviors.js +553 -0
- package/src/models/behaviorsValidator.js +186 -0
- package/src/models/compatibility.js +133 -0
- package/src/models/dryRunValidator.js +261 -0
- package/src/models/filesystemBackedImpostersRepository.js +908 -0
- package/src/models/http/baseHttpServer.js +207 -0
- package/src/models/http/headersMap.js +87 -0
- package/src/models/http/httpProxy.js +230 -0
- package/src/models/http/httpRequest.js +82 -0
- package/src/models/http/httpServer.js +18 -0
- package/src/models/http/index.js +18 -0
- package/src/models/https/cert/mb-cert.pem +20 -0
- package/src/models/https/cert/mb-csr.pem +16 -0
- package/src/models/https/cert/mb-key.pem +27 -0
- package/src/models/https/httpsServer.js +42 -0
- package/src/models/https/index.js +18 -0
- package/src/models/imposter.js +243 -0
- package/src/models/imposterPrinter.js +120 -0
- package/src/models/impostersRepository.js +49 -0
- package/src/models/inMemoryImpostersRepository.js +418 -0
- package/src/models/jsonpath.js +44 -0
- package/src/models/mbConnection.js +107 -0
- package/src/models/predicates.js +438 -0
- package/src/models/protocols.js +242 -0
- package/src/models/responseResolver.js +398 -0
- package/src/models/smtp/index.js +16 -0
- package/src/models/smtp/smtpRequest.js +60 -0
- package/src/models/smtp/smtpServer.js +109 -0
- package/src/models/tcp/index.js +18 -0
- package/src/models/tcp/tcpProxy.js +110 -0
- package/src/models/tcp/tcpRequest.js +23 -0
- package/src/models/tcp/tcpServer.js +156 -0
- package/src/models/tcp/tcpValidator.js +19 -0
- package/src/models/xpath.js +95 -0
- package/src/mountebank.js +245 -0
- package/src/public/images/arrow_down.png +0 -0
- package/src/public/images/arrow_up.png +0 -0
- package/src/public/images/book.jpg +0 -0
- package/src/public/images/dataflow.png +0 -0
- package/src/public/images/favicon.ico +0 -0
- package/src/public/images/forkme_right_orange_ff7600.png +0 -0
- package/src/public/images/mountebank.png +0 -0
- package/src/public/images/overview.gif +0 -0
- package/src/public/images/quote.png +0 -0
- package/src/public/images/tw-logo.png +0 -0
- package/src/public/scripts/jquery/jquery-3.6.1.min.js +2 -0
- package/src/public/scripts/urlHashHandler.js +31 -0
- package/src/public/stylesheets/application.css +424 -0
- package/src/public/stylesheets/ie.css +14 -0
- package/src/public/stylesheets/imposters.css +121 -0
- package/src/public/stylesheets/jqueryui/1.10.4/themes/smoothness/jquery-ui.css +1178 -0
- package/src/util/combinators.js +68 -0
- package/src/util/date.js +51 -0
- package/src/util/errors.js +55 -0
- package/src/util/helpers.js +131 -0
- package/src/util/inherit.js +28 -0
- package/src/util/ip.js +54 -0
- package/src/util/logger.js +83 -0
- package/src/util/middleware.js +256 -0
- package/src/util/scopedLogger.js +47 -0
- package/src/views/_footer.ejs +20 -0
- package/src/views/_header.ejs +113 -0
- package/src/views/_imposter.ejs +8 -0
- package/src/views/config.ejs +71 -0
- package/src/views/docs/api/behaviors/copy.ejs +427 -0
- package/src/views/docs/api/behaviors/decorate.ejs +182 -0
- package/src/views/docs/api/behaviors/lookup.ejs +220 -0
- package/src/views/docs/api/behaviors/shellTransform.ejs +153 -0
- package/src/views/docs/api/behaviors/wait.ejs +121 -0
- package/src/views/docs/api/behaviors.ejs +141 -0
- package/src/views/docs/api/contracts/addStub-description.ejs +10 -0
- package/src/views/docs/api/contracts/addStub.ejs +10 -0
- package/src/views/docs/api/contracts/config-description.ejs +32 -0
- package/src/views/docs/api/contracts/config.ejs +23 -0
- package/src/views/docs/api/contracts/home-description.ejs +18 -0
- package/src/views/docs/api/contracts/home.ejs +13 -0
- package/src/views/docs/api/contracts/imposter-description.ejs +439 -0
- package/src/views/docs/api/contracts/imposter.ejs +182 -0
- package/src/views/docs/api/contracts/imposters-description.ejs +13 -0
- package/src/views/docs/api/contracts/imposters.ejs +13 -0
- package/src/views/docs/api/contracts/logs-description.ejs +3 -0
- package/src/views/docs/api/contracts/logs.ejs +14 -0
- package/src/views/docs/api/contracts/stub-description.ejs +4 -0
- package/src/views/docs/api/contracts/stub.ejs +7 -0
- package/src/views/docs/api/contracts/stubs-description.ejs +4 -0
- package/src/views/docs/api/contracts/stubs.ejs +11 -0
- package/src/views/docs/api/contracts.ejs +133 -0
- package/src/views/docs/api/errors.ejs +64 -0
- package/src/views/docs/api/fault/connectionReset.ejs +31 -0
- package/src/views/docs/api/fault/randomDataThenClose.ejs +31 -0
- package/src/views/docs/api/faults.ejs +57 -0
- package/src/views/docs/api/injection.ejs +426 -0
- package/src/views/docs/api/json.ejs +205 -0
- package/src/views/docs/api/jsonpath.ejs +210 -0
- package/src/views/docs/api/mocks.ejs +130 -0
- package/src/views/docs/api/overview.ejs +968 -0
- package/src/views/docs/api/predicates/and.ejs +62 -0
- package/src/views/docs/api/predicates/contains.ejs +64 -0
- package/src/views/docs/api/predicates/deepEquals.ejs +114 -0
- package/src/views/docs/api/predicates/endsWith.ejs +66 -0
- package/src/views/docs/api/predicates/equals.ejs +125 -0
- package/src/views/docs/api/predicates/exists.ejs +118 -0
- package/src/views/docs/api/predicates/inject.ejs +67 -0
- package/src/views/docs/api/predicates/matches.ejs +66 -0
- package/src/views/docs/api/predicates/not.ejs +52 -0
- package/src/views/docs/api/predicates/or.ejs +79 -0
- package/src/views/docs/api/predicates/startsWith.ejs +62 -0
- package/src/views/docs/api/predicates.ejs +382 -0
- package/src/views/docs/api/proxies.ejs +191 -0
- package/src/views/docs/api/proxy/addDecorateBehavior.ejs +115 -0
- package/src/views/docs/api/proxy/addWaitBehavior.ejs +96 -0
- package/src/views/docs/api/proxy/injectHeaders.ejs +91 -0
- package/src/views/docs/api/proxy/predicateGenerators.ejs +600 -0
- package/src/views/docs/api/proxy/proxyModes.ejs +495 -0
- package/src/views/docs/api/stubs.ejs +391 -0
- package/src/views/docs/api/xpath.ejs +281 -0
- package/src/views/docs/cli/configFiles.ejs +133 -0
- package/src/views/docs/cli/customFormatters.ejs +53 -0
- package/src/views/docs/cli/help.ejs +6 -0
- package/src/views/docs/cli/replay.ejs +42 -0
- package/src/views/docs/cli/restart.ejs +10 -0
- package/src/views/docs/cli/save.ejs +68 -0
- package/src/views/docs/cli/start.ejs +234 -0
- package/src/views/docs/cli/stop.ejs +32 -0
- package/src/views/docs/commandLine.ejs +93 -0
- package/src/views/docs/communityExtensions.ejs +233 -0
- package/src/views/docs/gettingStarted.ejs +146 -0
- package/src/views/docs/mentalModel.ejs +51 -0
- package/src/views/docs/protocols/custom.ejs +231 -0
- package/src/views/docs/protocols/http.ejs +238 -0
- package/src/views/docs/protocols/https.ejs +246 -0
- package/src/views/docs/protocols/smtp.ejs +142 -0
- package/src/views/docs/protocols/tcp.ejs +431 -0
- package/src/views/docs/security.ejs +38 -0
- package/src/views/faqs.ejs +65 -0
- package/src/views/feed.ejs +33 -0
- package/src/views/imposter.ejs +22 -0
- package/src/views/imposters.ejs +33 -0
- package/src/views/index.ejs +89 -0
- package/src/views/license.ejs +30 -0
- package/src/views/logs.ejs +77 -0
- package/src/views/releases/v1.1.0.ejs +55 -0
- package/src/views/releases/v1.1.36.ejs +84 -0
- package/src/views/releases/v1.1.72.ejs +92 -0
- package/src/views/releases/v1.10.0.ejs +108 -0
- package/src/views/releases/v1.11.0.ejs +109 -0
- package/src/views/releases/v1.12.0.ejs +96 -0
- package/src/views/releases/v1.13.0.ejs +118 -0
- package/src/views/releases/v1.14.0.ejs +107 -0
- package/src/views/releases/v1.14.1.ejs +94 -0
- package/src/views/releases/v1.15.0.ejs +113 -0
- package/src/views/releases/v1.16.0.ejs +104 -0
- package/src/views/releases/v1.2.0.ejs +78 -0
- package/src/views/releases/v1.2.103.ejs +86 -0
- package/src/views/releases/v1.2.122.ejs +86 -0
- package/src/views/releases/v1.2.30.ejs +84 -0
- package/src/views/releases/v1.2.45.ejs +84 -0
- package/src/views/releases/v1.2.56.ejs +79 -0
- package/src/views/releases/v1.3.0.ejs +86 -0
- package/src/views/releases/v1.3.1.ejs +100 -0
- package/src/views/releases/v1.4.0.ejs +96 -0
- package/src/views/releases/v1.4.1.ejs +103 -0
- package/src/views/releases/v1.4.2.ejs +100 -0
- package/src/views/releases/v1.4.3.ejs +113 -0
- package/src/views/releases/v1.5.0.ejs +104 -0
- package/src/views/releases/v1.5.1.ejs +91 -0
- package/src/views/releases/v1.6.0.ejs +109 -0
- package/src/views/releases/v1.7.0.ejs +113 -0
- package/src/views/releases/v1.7.1.ejs +90 -0
- package/src/views/releases/v1.7.2.ejs +96 -0
- package/src/views/releases/v1.8.0.ejs +121 -0
- package/src/views/releases/v1.9.0.ejs +111 -0
- package/src/views/releases/v2.0.0.ejs +159 -0
- package/src/views/releases/v2.1.0.ejs +121 -0
- package/src/views/releases/v2.1.1.ejs +106 -0
- package/src/views/releases/v2.1.2.ejs +84 -0
- package/src/views/releases/v2.2.0.ejs +115 -0
- package/src/views/releases/v2.2.1.ejs +102 -0
- package/src/views/releases/v2.3.0.ejs +121 -0
- package/src/views/releases/v2.3.1.ejs +100 -0
- package/src/views/releases/v2.3.2.ejs +102 -0
- package/src/views/releases/v2.3.3.ejs +97 -0
- package/src/views/releases/v2.4.0.ejs +114 -0
- package/src/views/releases/v2.5.0.ejs +51 -0
- package/src/views/releases/v2.6.0.ejs +35 -0
- package/src/views/releases/v2.7.0.ejs +32 -0
- package/src/views/releases/v2.8.0.ejs +36 -0
- package/src/views/releases/v2.8.1.ejs +7 -0
- package/src/views/releases/v2.8.2.ejs +26 -0
- package/src/views/releases/v2.9.0.ejs +32 -0
- package/src/views/releases/v2.9.1.ejs +10 -0
- package/src/views/releases.ejs +26 -0
- package/src/views/sitemap.ejs +36 -0
- package/src/views/support.ejs +14 -0
|
@@ -0,0 +1,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
|
+
<books xmlns:isbn="http://schemas.isbn.org/ns/1999/basic.dtd">
|
|
230
|
+
<book>
|
|
231
|
+
<isbn:title><strong class='highlight1'>Game of Thrones</strong></isbn:title>
|
|
232
|
+
<isbn:summary>Dragons and political intrigue</isbn:summary>
|
|
233
|
+
</book>
|
|
234
|
+
<book>
|
|
235
|
+
<isbn:title>Harry Potter</isbn:title>
|
|
236
|
+
<isbn:summary>Dragons and a boy wizard</isbn:summary>
|
|
237
|
+
</book>
|
|
238
|
+
<book>
|
|
239
|
+
<isbn:title>The Hobbit</isbn:title>
|
|
240
|
+
<isbn:summary>A dragon and short people</isbn:summary>
|
|
241
|
+
</book>
|
|
242
|
+
</books></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 < 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 < 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>
|