@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,391 @@
|
|
|
1
|
+
<%
|
|
2
|
+
title = 'stubs'
|
|
3
|
+
description = 'Creating canned responses using mountebank'
|
|
4
|
+
%>
|
|
5
|
+
|
|
6
|
+
<%- include('../../_header') -%>
|
|
7
|
+
|
|
8
|
+
<h1>Stub Responses</h1>
|
|
9
|
+
|
|
10
|
+
<p>Stubs are a type of test double that return a canned response based on the request.
|
|
11
|
+
mountebank allows you to define a list of stubs when creating an imposter. mountebank
|
|
12
|
+
associates both a list of <em>responses</em> and a list of
|
|
13
|
+
<a href='/docs/api/predicates'><em>predicates</em></a> with each stub.</p>
|
|
14
|
+
|
|
15
|
+
<p>It doesn't make sense to create an empty array of responses, but each response is under no
|
|
16
|
+
obligation to override the defaults (every protocol defines a default for every response field;
|
|
17
|
+
see the protocol-specific documentation pages linked to from the sidebar for more details).
|
|
18
|
+
The responses array defines a circular buffer - every time the stub is used for the request,
|
|
19
|
+
the first response is pulled from the front of the <code>responses</code> array, evaluated, and pushed
|
|
20
|
+
to the back of the array. This elegantly does what you want. In the common case, when you
|
|
21
|
+
always want to return the same response, you just add one response to the array.
|
|
22
|
+
More complex scenarios will require that the same endpoint returns a sequence of
|
|
23
|
+
different responses for the same predicates. Simply add them all to the array in order.
|
|
24
|
+
When the sequence finishes, it will start over. More complexity can be added by simply
|
|
25
|
+
adding more responses to the array without complicating the contract.</p>
|
|
26
|
+
|
|
27
|
+
<h2>Response Types</h2>
|
|
28
|
+
|
|
29
|
+
<p>Each stub response is defined by a specific response type that defines the behavior of the
|
|
30
|
+
response. The response types currently supported are:</p>
|
|
31
|
+
|
|
32
|
+
<table>
|
|
33
|
+
<tr>
|
|
34
|
+
<th>Response Type</th>
|
|
35
|
+
<th>Description</th>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td><code>is</code></td>
|
|
39
|
+
<td>Merges the specified response fields with the response defaults (see the protocol
|
|
40
|
+
page linked to from the sidebar on the left for the defaults).</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr>
|
|
43
|
+
<td><code>proxy</code></td>
|
|
44
|
+
<td>Proxies the request to the specified destination and returns the response. The
|
|
45
|
+
response is saved and can be replayed on subsequent calls.</td>
|
|
46
|
+
</tr>
|
|
47
|
+
<tr>
|
|
48
|
+
<td><code>inject</code></td>
|
|
49
|
+
<td>Allows you to inject a JavaScript function to create the response object.</td>
|
|
50
|
+
</tr>
|
|
51
|
+
<tr>
|
|
52
|
+
<td><code>fault</code></td>
|
|
53
|
+
<td>Allows you to specify a connection fault to occur instead of a normal response.</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</table>
|
|
56
|
+
|
|
57
|
+
<p>See the <a href='/docs/api/proxies'>proxy</a>, <a href='/docs/api/injection'>
|
|
58
|
+
injection</a> and <a href='/docs/api/faults'>fault</a> pages for detailed examples of those response types, and the
|
|
59
|
+
<a href='/docs/api/predicates'>predicates</a> page for examples of stubs with predicates.
|
|
60
|
+
Multiple stubs only make sense with predicates.</p>
|
|
61
|
+
|
|
62
|
+
<p>Responses can be parameterized. Right now, only one parameter is supported:</p>
|
|
63
|
+
|
|
64
|
+
<table>
|
|
65
|
+
<tr>
|
|
66
|
+
<th>Parameter</th>
|
|
67
|
+
<th>Default</th>
|
|
68
|
+
<th>Description</th>
|
|
69
|
+
</tr>
|
|
70
|
+
<tr>
|
|
71
|
+
<td><code>repeat</code></td>
|
|
72
|
+
<td><code>1</code></td>
|
|
73
|
+
<td>Repeats the response the given number of times.</td>
|
|
74
|
+
</tr>
|
|
75
|
+
</table>
|
|
76
|
+
|
|
77
|
+
<p>Stubs can be decorated by adding to the <code>behaviors</code> array. See the
|
|
78
|
+
<a href='/docs/api/behaviors'>behaviors</a> page for more details.</p>
|
|
79
|
+
|
|
80
|
+
<h2>Example</h2>
|
|
81
|
+
|
|
82
|
+
<p>Let's create an http imposter with a couple of <code>is</code> response types. It will simulate
|
|
83
|
+
a RESTful endpoint that creates a customer the first time it's called and returns a 400
|
|
84
|
+
the second time it's called because the email already exists. We add a second stub that returns a
|
|
85
|
+
404 on any request that isn't a <code>POST</code> to <code>/customers/123</code></p>
|
|
86
|
+
|
|
87
|
+
<testScenario name='example'>
|
|
88
|
+
<step type='http'>
|
|
89
|
+
<pre><code>POST /imposters HTTP/1.1
|
|
90
|
+
Host: localhost:<%= port %>
|
|
91
|
+
Accept: application/json
|
|
92
|
+
Content-Type: application/json
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
"port": 4545,
|
|
96
|
+
"protocol": "http",
|
|
97
|
+
"recordRequests": true,
|
|
98
|
+
"stubs": [
|
|
99
|
+
{
|
|
100
|
+
"predicates": [
|
|
101
|
+
{
|
|
102
|
+
"equals": {
|
|
103
|
+
"method": "POST",
|
|
104
|
+
"path": "/customers/123"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"responses": [
|
|
109
|
+
{
|
|
110
|
+
"is": {
|
|
111
|
+
"statusCode": 201,
|
|
112
|
+
"headers": {
|
|
113
|
+
"Location": "http://localhost:4545/customers/123",
|
|
114
|
+
"Content-Type": "application/xml"
|
|
115
|
+
},
|
|
116
|
+
"body": "<customer><email>customer@test.com</email></customer>"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"is": {
|
|
121
|
+
"statusCode": 400,
|
|
122
|
+
"headers": {
|
|
123
|
+
"Content-Type": "application/xml"
|
|
124
|
+
},
|
|
125
|
+
"body": "<error>email already exists</error>"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"responses": [
|
|
132
|
+
{
|
|
133
|
+
"is": { "statusCode": 404 }
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}</code></pre>
|
|
139
|
+
</step>
|
|
140
|
+
|
|
141
|
+
<p>Let's assume the application under test makes the initial call...</p>
|
|
142
|
+
|
|
143
|
+
<step type='http'>
|
|
144
|
+
<pre><code>POST /customers/123 HTTP/1.1
|
|
145
|
+
Host: localhost:4545
|
|
146
|
+
Accept: application/xml
|
|
147
|
+
Content-Type: application/xml
|
|
148
|
+
|
|
149
|
+
<customer>
|
|
150
|
+
<email>customer@test.com</email>
|
|
151
|
+
</customer></code></pre>
|
|
152
|
+
|
|
153
|
+
<assertResponse>
|
|
154
|
+
<pre><code>HTTP/1.1 201 Created
|
|
155
|
+
Location: http://localhost:4545/customers/123
|
|
156
|
+
Content-Type: application/xml
|
|
157
|
+
Connection: close
|
|
158
|
+
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
|
|
159
|
+
Transfer-Encoding: chunked
|
|
160
|
+
|
|
161
|
+
<customer><email>customer@test.com</email></customer></code></pre>
|
|
162
|
+
</assertResponse>
|
|
163
|
+
</step>
|
|
164
|
+
|
|
165
|
+
<p>...and the second call:</p>
|
|
166
|
+
|
|
167
|
+
<step type='http'>
|
|
168
|
+
<pre><code>POST /customers/123 HTTP/1.1
|
|
169
|
+
Host: localhost:4545
|
|
170
|
+
Accept: application/xml
|
|
171
|
+
Content-Type: application/xml
|
|
172
|
+
|
|
173
|
+
<customer>
|
|
174
|
+
<email>customer@test.com</email>
|
|
175
|
+
</customer></code></pre>
|
|
176
|
+
|
|
177
|
+
<assertResponse>
|
|
178
|
+
<pre><code>HTTP/1.1 400 Bad Request
|
|
179
|
+
Content-Type: application/xml
|
|
180
|
+
Connection: close
|
|
181
|
+
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
|
|
182
|
+
Transfer-Encoding: chunked
|
|
183
|
+
|
|
184
|
+
<error>email already exists</error></code></pre>
|
|
185
|
+
</assertResponse>
|
|
186
|
+
</step>
|
|
187
|
+
|
|
188
|
+
<p>Now let's see the <code>requests</code> that mountebank
|
|
189
|
+
has saved for you:</p>
|
|
190
|
+
|
|
191
|
+
<step type='http'>
|
|
192
|
+
<pre><code>GET /imposters/4545 HTTP/1.1
|
|
193
|
+
Host: localhost:<%= port %>
|
|
194
|
+
Accept: application/json</code></pre>
|
|
195
|
+
|
|
196
|
+
<assertResponse>
|
|
197
|
+
<pre><code>HTTP/1.1 200 OK
|
|
198
|
+
Vary: Accept
|
|
199
|
+
Content-Type: application/json; charset=utf-8
|
|
200
|
+
Content-Length: <volatile>4212</volatile>
|
|
201
|
+
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
|
|
202
|
+
Connection: keep-alive
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
"protocol": "http",
|
|
206
|
+
"port": 4545,
|
|
207
|
+
"numberOfRequests": 2,
|
|
208
|
+
"recordRequests": true,
|
|
209
|
+
"requests": [
|
|
210
|
+
{
|
|
211
|
+
"requestFrom": "<volatile>::ffff:127.0.0.1:60523</volatile>",
|
|
212
|
+
"method": "POST",
|
|
213
|
+
"path": "/customers/123",
|
|
214
|
+
"query": {},
|
|
215
|
+
"headers": {
|
|
216
|
+
"Host": "localhost:4545",
|
|
217
|
+
"Accept": "application/xml",
|
|
218
|
+
"Content-Type": "application/xml",
|
|
219
|
+
"Connection": "keep-alive",
|
|
220
|
+
"Transfer-Encoding": "chunked"
|
|
221
|
+
},
|
|
222
|
+
"body": "<customer>\n <email>customer@test.com</email>\n</customer>",
|
|
223
|
+
"ip": "<volatile>::ffff:127.0.0.1</volatile>",
|
|
224
|
+
"timestamp": "<volatile>2014-01-09T02:30:31.022Z</volatile>"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"requestFrom": "<volatile>::ffff:127.0.0.1:60523</volatile>",
|
|
228
|
+
"method": "POST",
|
|
229
|
+
"path": "/customers/123",
|
|
230
|
+
"query": {},
|
|
231
|
+
"headers": {
|
|
232
|
+
"Host": "localhost:4545",
|
|
233
|
+
"Accept": "application/xml",
|
|
234
|
+
"Content-Type": "application/xml",
|
|
235
|
+
"Connection": "keep-alive",
|
|
236
|
+
"Transfer-Encoding": "chunked"
|
|
237
|
+
},
|
|
238
|
+
"body": "<customer>\n <email>customer@test.com</email>\n</customer>",
|
|
239
|
+
"ip": "<volatile>::ffff:127.0.0.1</volatile>",
|
|
240
|
+
"timestamp": "<volatile>2014-01-09T02:30:31.043Z</volatile>"
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"stubs": [
|
|
244
|
+
{
|
|
245
|
+
"predicates": [
|
|
246
|
+
{
|
|
247
|
+
"equals": {
|
|
248
|
+
"method": "POST",
|
|
249
|
+
"path": "/customers/123"
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
],
|
|
253
|
+
"responses": [
|
|
254
|
+
{
|
|
255
|
+
"is": {
|
|
256
|
+
"statusCode": 201,
|
|
257
|
+
"headers": {
|
|
258
|
+
"Location": "http://localhost:4545/customers/123",
|
|
259
|
+
"Content-Type": "application/xml"
|
|
260
|
+
},
|
|
261
|
+
"body": "<customer><email>customer@test.com</email></customer>"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"is": {
|
|
266
|
+
"statusCode": 400,
|
|
267
|
+
"headers": {
|
|
268
|
+
"Content-Type": "application/xml"
|
|
269
|
+
},
|
|
270
|
+
"body": "<error>email already exists</error>"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
],
|
|
274
|
+
"_links": {
|
|
275
|
+
"self": { "href": "http://localhost:<%= port %>/imposters/4545/stubs/0" }
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"responses": [
|
|
280
|
+
{
|
|
281
|
+
"is": { "statusCode": 404 }
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
"_links": {
|
|
285
|
+
"self": { "href": "http://localhost:<%= port %>/imposters/4545/stubs/1" }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
"_links": {
|
|
290
|
+
"self": { "href": "http://localhost:<%= port %>/imposters/4545" },
|
|
291
|
+
"stubs": { "href": "http://localhost:<%= port %>/imposters/4545/stubs" }
|
|
292
|
+
}
|
|
293
|
+
}</code></pre>
|
|
294
|
+
</assertResponse>
|
|
295
|
+
</step>
|
|
296
|
+
|
|
297
|
+
<step type='http'>
|
|
298
|
+
|
|
299
|
+
<code class='hidden'>DELETE /imposters/4545 HTTP/1.1
|
|
300
|
+
Host: localhost:<%= port %></code>
|
|
301
|
+
</step>
|
|
302
|
+
</testScenario>
|
|
303
|
+
|
|
304
|
+
<h2>Repeat example</h2>
|
|
305
|
+
|
|
306
|
+
<p>The <code>repeat</code> behavior allows certain responses to return a certain number of times before
|
|
307
|
+
moving on to the next response in the array. In the below example, the first response is configured to
|
|
308
|
+
return 2 times before advancing to the second response:</p>
|
|
309
|
+
|
|
310
|
+
<testScenario name='repeat'>
|
|
311
|
+
<step type='http'>
|
|
312
|
+
<pre><code>POST /imposters HTTP/1.1
|
|
313
|
+
Host: localhost:<%= port %>
|
|
314
|
+
Accept: application/json
|
|
315
|
+
Content-Type: application/json
|
|
316
|
+
|
|
317
|
+
{
|
|
318
|
+
"port": 7777,
|
|
319
|
+
"protocol": "http",
|
|
320
|
+
"stubs": [
|
|
321
|
+
{
|
|
322
|
+
"responses": [
|
|
323
|
+
{
|
|
324
|
+
"is": {
|
|
325
|
+
"body": "This will repeat 2 times"
|
|
326
|
+
},
|
|
327
|
+
"repeat": 2
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"is": {
|
|
331
|
+
"body": "Then this will return"
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
}</code></pre>
|
|
338
|
+
</step>
|
|
339
|
+
|
|
340
|
+
<p>Now the first response will return 2 times before returning the second response.</p>
|
|
341
|
+
|
|
342
|
+
<step type='http'>
|
|
343
|
+
<pre><code>GET / HTTP/1.1
|
|
344
|
+
Host: localhost:7777</code></pre>
|
|
345
|
+
|
|
346
|
+
<assertResponse>
|
|
347
|
+
<pre><code>HTTP/1.1 200 OK
|
|
348
|
+
Connection: close
|
|
349
|
+
Date: <volatile>Thu, 01 Jan 2015 02:30:31 GMT</volatile>
|
|
350
|
+
Transfer-Encoding: chunked
|
|
351
|
+
|
|
352
|
+
This will repeat 2 times</code></pre>
|
|
353
|
+
</assertResponse>
|
|
354
|
+
</step>
|
|
355
|
+
|
|
356
|
+
<step type='http'>
|
|
357
|
+
<pre><code>GET / HTTP/1.1
|
|
358
|
+
Host: localhost:7777</code></pre>
|
|
359
|
+
|
|
360
|
+
<assertResponse>
|
|
361
|
+
<pre><code>HTTP/1.1 200 OK
|
|
362
|
+
Connection: close
|
|
363
|
+
Date: <volatile>Thu, 01 Jan 2015 02:30:31 GMT</volatile>
|
|
364
|
+
Transfer-Encoding: chunked
|
|
365
|
+
|
|
366
|
+
This will repeat 2 times</code></pre>
|
|
367
|
+
</assertResponse>
|
|
368
|
+
</step>
|
|
369
|
+
|
|
370
|
+
<step type='http'>
|
|
371
|
+
<pre><code>GET / HTTP/1.1
|
|
372
|
+
Host: localhost:7777</code></pre>
|
|
373
|
+
|
|
374
|
+
<assertResponse>
|
|
375
|
+
<pre><code>HTTP/1.1 200 OK
|
|
376
|
+
Connection: close
|
|
377
|
+
Date: <volatile>Thu, 01 Jan 2015 02:30:31 GMT</volatile>
|
|
378
|
+
Transfer-Encoding: chunked
|
|
379
|
+
|
|
380
|
+
Then this will return</code></pre>
|
|
381
|
+
</assertResponse>
|
|
382
|
+
</step>
|
|
383
|
+
|
|
384
|
+
<step type='http'>
|
|
385
|
+
<code class='hidden'>DELETE /imposters/7777 HTTP/1.1
|
|
386
|
+
Host: localhost:<%= port %></code>
|
|
387
|
+
</step>
|
|
388
|
+
</testScenario>
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
<%- include('../../_footer') -%>
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<%
|
|
2
|
+
title = 'xpath'
|
|
3
|
+
description = 'Using XPath in mountebank predicates'
|
|
4
|
+
%>
|
|
5
|
+
|
|
6
|
+
<%- include('../../_header') -%>
|
|
7
|
+
|
|
8
|
+
<h1>Using XPath</h1>
|
|
9
|
+
|
|
10
|
+
<p>It is common for mountebank to see XML documents in his line of business, and he suspects
|
|
11
|
+
the same may be true for you. With the goal of making XML easier to work with, mountebank
|
|
12
|
+
accepts an <code>xpath</code> predicate parameter. This parameter narrows the scope of the predicate
|
|
13
|
+
value to a value matched by the xpath selector, much like the <a href='/docs/api/predicates'>
|
|
14
|
+
<code>except</code> parameter</a>. mountebank suggests avoiding using this parameter on fields
|
|
15
|
+
that are not XML documents. He also counsels avoiding the temptation to use it with binary protocols.</p>
|
|
16
|
+
|
|
17
|
+
<p>mountebank uses the following fields with xpath:</p>
|
|
18
|
+
|
|
19
|
+
<table>
|
|
20
|
+
<tr>
|
|
21
|
+
<th>Field</th>
|
|
22
|
+
<th>Required?</th>
|
|
23
|
+
<th>Description</th>
|
|
24
|
+
</tr>
|
|
25
|
+
<tr>
|
|
26
|
+
<td><code>selector</code></td>
|
|
27
|
+
<td>Yes</td>
|
|
28
|
+
<td>The XPath selector</td>
|
|
29
|
+
</tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<td><code>ns</code></td>
|
|
32
|
+
<td>No</td>
|
|
33
|
+
<td>The XPath namespace map, aliasing a prefix to a URL, which allows you to use the prefix in
|
|
34
|
+
the <code>selector</code>.</td>
|
|
35
|
+
</tr>
|
|
36
|
+
</table>
|
|
37
|
+
|
|
38
|
+
<p>You can send the parameter without namespaces...</p>
|
|
39
|
+
|
|
40
|
+
<pre><code>{
|
|
41
|
+
"equals": { "body": "Harry Potter" },
|
|
42
|
+
"xpath": { "selector": "//title" }
|
|
43
|
+
}</code></pre>
|
|
44
|
+
|
|
45
|
+
<p>...or with namespaces:</p>
|
|
46
|
+
|
|
47
|
+
<pre><code>{
|
|
48
|
+
"equals": { "body": "Harry Potter" },
|
|
49
|
+
"xpath": {
|
|
50
|
+
"selector": "//a:title"
|
|
51
|
+
"ns": {
|
|
52
|
+
"a": "http://example.com/book"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}</code></pre>
|
|
56
|
+
|
|
57
|
+
<p>The xpath parameter follows the same semantics as those obeyed for multi-valued keys described on the
|
|
58
|
+
<a href='/docs/api/predicates'>main predicates page</a>, like those observed when a querystring has
|
|
59
|
+
the same key multiple times. Since the xpath selector can potentially match multiple nodes in the XML
|
|
60
|
+
document, this is an important point to make. <code>deepEquals</code> will require all the values to match
|
|
61
|
+
(although the order isn't important). All other predicates will match if any node value matches. The examples below
|
|
62
|
+
explore these semantics.</p>
|
|
63
|
+
|
|
64
|
+
<h2>Examples</h2>
|
|
65
|
+
|
|
66
|
+
<p>Let's create an HTTP imposter with multiple stubs. We'll use redundant predicates simply to
|
|
67
|
+
demonstrate various ways to use the <code>xpath</code> parameter. Where applicable, a
|
|
68
|
+
<code>comment</code> field is added to the JSON to explain what's going on. Like all superfluous
|
|
69
|
+
fields, mountebank will ignore it.</p>
|
|
70
|
+
|
|
71
|
+
<testScenario name='xpath'>
|
|
72
|
+
<step type='http'>
|
|
73
|
+
<pre><code>POST /imposters HTTP/1.1
|
|
74
|
+
Host: localhost:<%= port %>
|
|
75
|
+
Accept: application/json
|
|
76
|
+
Content-Type: application/json
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
"port": 4545,
|
|
80
|
+
"protocol": "http",
|
|
81
|
+
"stubs": [<strong class='highlight1'>
|
|
82
|
+
{
|
|
83
|
+
"responses": [{ "is": { "body": "Basic xpath usage" } }],
|
|
84
|
+
"predicates": [
|
|
85
|
+
{
|
|
86
|
+
"equals": { "body": "Harry Potter" },
|
|
87
|
+
"xpath": { "selector": "//title" },
|
|
88
|
+
"caseSensitive": true,
|
|
89
|
+
"comment": "case sensitivity applies to the selector as well as the value"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"equals": { "body": "POTTER" },
|
|
93
|
+
"xpath": { "selector": "//TITLE" },
|
|
94
|
+
"except": "HARRY ",
|
|
95
|
+
"comment": "The except regular expression is removed from the value before matching"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"matches": { "body": "^Harry" },
|
|
99
|
+
"xpath": { "selector": "//title" }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"exists": { "body": true },
|
|
103
|
+
"xpath": { "selector": "//title" },
|
|
104
|
+
"comment": "body must exist (e.g. be empty) AND the xpath must match at least one node"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"exists": { "body": false },
|
|
108
|
+
"xpath": { "selector": "//title/@first" },
|
|
109
|
+
"comment": "body must not exist (e.g. be empty) OR the xpath must not match any nodes"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"equals": { "body": 3 },
|
|
113
|
+
"xpath": { "selector": "count(//title)" },
|
|
114
|
+
"comment": "number results from xpath selectors are fine..."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"equals": { "body": true },
|
|
118
|
+
"xpath": { "selector": "boolean(//title)" },
|
|
119
|
+
"comment": "...as are boolean results"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}</strong>,<strong class='highlight2'>
|
|
123
|
+
{
|
|
124
|
+
"responses": [{ "is": { "body": "xpath with namespaces" } }],
|
|
125
|
+
"predicates": [
|
|
126
|
+
{
|
|
127
|
+
"contains": { "body": "dragons" },
|
|
128
|
+
"xpath": {
|
|
129
|
+
"selector": "//isbn:summary",
|
|
130
|
+
"ns": {
|
|
131
|
+
"isbn": "http://schemas.isbn.org/ns/1999/basic.dtd"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"contains": { "body": "dragons" },
|
|
137
|
+
"xpath": {
|
|
138
|
+
"selector": "//*[local-name(.)='summary' and namespace-uri(.)='http://schemas.isbn.org/ns/1999/basic.dtd']"
|
|
139
|
+
},
|
|
140
|
+
"comment": "You can use XPath without namespace aliases if you're so inclined"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"exists": { "body": false },
|
|
144
|
+
"xpath": { "selector": "//title/@first" },
|
|
145
|
+
"comment": "This lets us test out the third stub without running into a conflict"
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}</strong>,<strong class='highlight3'>
|
|
149
|
+
{
|
|
150
|
+
"responses": [{ "is": { "body": "xpath with attributes and the quirks of deepEquals" } }],
|
|
151
|
+
"predicates": [
|
|
152
|
+
{
|
|
153
|
+
"deepEquals": { "body": "3" },
|
|
154
|
+
"xpath": { "selector": "//books/@count" },
|
|
155
|
+
"comment": "deepEquals expects a string when there is only one match"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"deepEquals": { "body": ["false", "false", "true"] },
|
|
159
|
+
"xpath": { "selector": "//title/@first" },
|
|
160
|
+
"comment": "Use an array for deepEquals when there are multiple matches (same for a repeating querystring key)"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}</strong>
|
|
164
|
+
]
|
|
165
|
+
}</code></pre>
|
|
166
|
+
</step>
|
|
167
|
+
|
|
168
|
+
<p>The first predicate uses an xpath selector to navigate to all <code>title</code> elements
|
|
169
|
+
within the XML document within the <code>body</code> field. If <code>body</code> turns out not
|
|
170
|
+
to be an XML document, none of them will match. The <code>caseSensitive</code> parameter applies
|
|
171
|
+
both to the predicate value in <code>body</code> and to the <code>selector</code> field in
|
|
172
|
+
<code>xpath</code>. The <code>exists</code> predicate behavior is identical to its behavior with
|
|
173
|
+
other fields, except now the xpath selector must also match if <code>exists</code> is <code>true</code>,
|
|
174
|
+
or not match if <code>exists</code> is false.<p>
|
|
175
|
+
|
|
176
|
+
<p>Note that in the example below, there are multiple nodes matching the xpath selector. With all
|
|
177
|
+
predicates except <code>deepEquals</code>, it's sufficient that <i>any</i> of them match.</p>
|
|
178
|
+
|
|
179
|
+
<step type='http'>
|
|
180
|
+
<pre><code>POST / HTTP/1.1
|
|
181
|
+
Host: localhost:4545
|
|
182
|
+
|
|
183
|
+
<books xmlns:isbn="http://schemas.isbn.org/ns/1999/basic.dtd">
|
|
184
|
+
<book>
|
|
185
|
+
<title>Game of Thrones</title>
|
|
186
|
+
<isbn:summary>Dragons and political intrigue</isbn:summary>
|
|
187
|
+
</book>
|
|
188
|
+
<book>
|
|
189
|
+
<strong class='highlight1'><title>Harry Potter</title></strong>
|
|
190
|
+
<isbn:summary>Dragons and a boy wizard</isbn:summary>
|
|
191
|
+
</book>
|
|
192
|
+
<book>
|
|
193
|
+
<title>The Hobbit</title>
|
|
194
|
+
<isbn:summary>A dragon and short people</isbn:summary>
|
|
195
|
+
</book>
|
|
196
|
+
</books></code></pre>
|
|
197
|
+
|
|
198
|
+
<assertResponse>
|
|
199
|
+
<pre><code>HTTP/1.1 200 OK
|
|
200
|
+
Connection: close
|
|
201
|
+
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
|
|
202
|
+
Transfer-Encoding: chunked
|
|
203
|
+
|
|
204
|
+
<strong class='highlight1'>Basic xpath usage</strong></code></pre>
|
|
205
|
+
</assertResponse>
|
|
206
|
+
</step>
|
|
207
|
+
|
|
208
|
+
<p>The XML document above also would match the second stub's predicates, but mountebank
|
|
209
|
+
always uses the first stub that matches. To test the second stub, we'll namespace the
|
|
210
|
+
<code>title</code> nodes to get past the first stub.</p>
|
|
211
|
+
|
|
212
|
+
<step type='http'>
|
|
213
|
+
<pre><code>POST / HTTP/1.1
|
|
214
|
+
Host: localhost:4545
|
|
215
|
+
|
|
216
|
+
<books xmlns:isbn="http://schemas.isbn.org/ns/1999/basic.dtd">
|
|
217
|
+
<book>
|
|
218
|
+
<isbn:title>Game of Thrones</isbn:title>
|
|
219
|
+
<strong class='highlight2'><isbn:summary>Dragons and political intrigue</isbn:summary></strong>
|
|
220
|
+
</book>
|
|
221
|
+
<book>
|
|
222
|
+
<isbn:title>Harry Potter</isbn:title>
|
|
223
|
+
<strong class='highlight2'><isbn:summary>Dragons and a boy wizard</isbn:summary></strong>
|
|
224
|
+
</book>
|
|
225
|
+
<book>
|
|
226
|
+
<isbn:title>The Hobbit</isbn:title>
|
|
227
|
+
<isbn:summary>A dragon and short people</isbn:summary>
|
|
228
|
+
</book>
|
|
229
|
+
</books></code></pre>
|
|
230
|
+
|
|
231
|
+
<assertResponse>
|
|
232
|
+
<pre><code>HTTP/1.1 200 OK
|
|
233
|
+
Connection: close
|
|
234
|
+
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
|
|
235
|
+
Transfer-Encoding: chunked
|
|
236
|
+
|
|
237
|
+
<strong class='highlight2'>xpath with namespaces</strong></code></pre>
|
|
238
|
+
</assertResponse>
|
|
239
|
+
</step>
|
|
240
|
+
|
|
241
|
+
<p>The third stub will only match on a <code>first</code> attribute, and expects one
|
|
242
|
+
to be set to true and two to be set to false. In the interests of providing an unbiased
|
|
243
|
+
presentation, we'll simply denote which book was first published with this attribute.</p>
|
|
244
|
+
|
|
245
|
+
<step type='http'>
|
|
246
|
+
<pre><code>POST / HTTP/1.1
|
|
247
|
+
Host: localhost:4545
|
|
248
|
+
|
|
249
|
+
<books count="3" xmlns:isbn="http://schemas.isbn.org/ns/1999/basic.dtd">
|
|
250
|
+
<book>
|
|
251
|
+
<title <strong class='highlight3'>first="false"</strong>>Game of Thrones</title>
|
|
252
|
+
<isbn:summary>Dragons and political intrigue</isbn:summary>
|
|
253
|
+
</book>
|
|
254
|
+
<book>
|
|
255
|
+
<title <strong class='highlight3'>first="false"</strong>>Harry Potter</title>
|
|
256
|
+
<isbn:summary>Dragons and a boy wizard</isbn:summary>
|
|
257
|
+
</book>
|
|
258
|
+
<book>
|
|
259
|
+
<title <strong class='highlight3'>first="true"</strong>>The Hobbit</title>
|
|
260
|
+
<isbn:summary>A dragon and short people</isbn:summary>
|
|
261
|
+
</book>
|
|
262
|
+
</books></code></pre>
|
|
263
|
+
|
|
264
|
+
<assertResponse>
|
|
265
|
+
<pre><code>HTTP/1.1 200 OK
|
|
266
|
+
Connection: close
|
|
267
|
+
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
|
|
268
|
+
Transfer-Encoding: chunked
|
|
269
|
+
|
|
270
|
+
<strong class='highlight3'>xpath with attributes and the quirks of deepEquals</strong></code></pre>
|
|
271
|
+
</assertResponse>
|
|
272
|
+
</step>
|
|
273
|
+
|
|
274
|
+
<step type='http'>
|
|
275
|
+
<code class='hidden'>DELETE /imposters/4545 HTTP/1.1
|
|
276
|
+
Host: localhost:<%= port %>
|
|
277
|
+
Accept: application/json</code>
|
|
278
|
+
</step>
|
|
279
|
+
</testScenario>
|
|
280
|
+
|
|
281
|
+
<%- include('../../_footer') -%>
|