mountebank之Predicates
来源:互联网 发布:医疗器械认证软件 编辑:程序博客网 时间:2024/06/05 03:17
Predicates
In the absence of a predicate, a stub always matches, and there's never a reason to add more than one stub to an imposter. Predicates allow imposters to have much richer behavior by defining whether or not a stub matches a request. When multiple stubs are created on an imposter, the first stub that matches is selected.
Each predicate object contains one or more of the request fields as keys. Predicates are added to a stub in an array, and all predicates are AND'd together. The following predicate operators are allowed:
equals
The request field matches the predicatedeepEquals
Performs nested set equality on the request field, useful when the request field is an object (e.g. the query
field in http)contains
The request field contains the predicatestartsWith
The request field starts with the predicateendsWith
The request field ends with the predicatematches
The request field matches the JavaScript regular expression defined with the predicate.exists
If true
, the request field must exist. If false
, the request field must not exist.not
Inverts a predicateor
Logically or's two predicates togetherand
Logically and's two predicates togetherinject
Injects JavaScript to decide whether the request matches or not. See the injection page for more details.Predicates can be parameterized. mountebank accepts the following predicate parameters:
caseSensitive
false
Determines if the match is case sensitive or not. This includes keys for objects such as query parameters.except
""
Defines a regular expression that is stripped out of the request field before matching.xpath
null
Defines an object containing a selector
string and, optionally, an ns
object field that defines a namespace map.jsonpath
null
Defines an object containing a selector
stringSee the equals
example below to see the caseSensitive
and except
parameters in action. See the xpath pagefor xpath
examples. See the jsonpath page or jsonpath
examples.
Almost all predicates are scoped to a request field; see the protocol pages linked to from the sidebar to see the request fields. inject
is the sole exception. It takes a string function that accepts the entire request. See the injection page for details.
The predicates work intuitively for base64-encoded binary data as well by internally converting the base64-encoded string to a JSON string representing the byte array. For example, sending "AQIDBA==" will get translated to "[1,2,3,4]", and predicates expecting "AgM=" will get translated to "[2,3]". Even though "AQIDBA==" does not contain "AgM=", a contains
predicate will match, because "[1,2,3,4]" does contain "[2,3]". This works well for everything but matches
, because any regular expression operators get encoded as binary. mountebank recommends that you stay away from matches
if you're dealing in binary. In mountebank's experience, contains
is the most useful predicate for binary imposters, as even binary RPC data generally contains strings representing method names.
On occasion you may encounter multi-valued keys. This can be the case with querystrings that have repeating keys, for example ?key=first&key=second
. In those cases, deepEquals
will require all the values (in any order) to match. All other predicates will match if any value matches, so an equals
predicate will match with the value ofsecond
in the example above.
Matching XML or JSON
mountebank has special support for matching XML and JSON request fields, such as in an http body
or tcp data
field. Where XML or JSON predicates are used against string
fields, mountebank will attempt to parse the field as XML or JSON and apply the given predicate. If he is unable to parse the field, the predicate will fail; otherwise it will pass or fail according to the selected value.
See the xpath page for xpath
examples.
See the json page for json
examples.
Examples
The examples below use both HTTP and TCP imposters. The TCP examples use netcat (nc
) to send TCP data over a socket, which is like telnet
, but makes the output easier to script. The examples for binary imposters use the base64
command line tool to decode base64 to binary before sending to the socket.
Let's create an HTTP imposter with multiple stubs:
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4545, "protocol": "http", "stubs": [ { "responses": [{ "is": { "statusCode": 400 } }], "predicates": [ { "equals": { "method": "POST", "path": "/test", "query": { "first": "1", "second": "2" }, "headers": { "Accept": "text/plain" } } }, { "equals": { "body": "hello, world" }, "caseSensitive": true, "except": "!$" } ] }, { "responses": [{ "is": { "statusCode": 406 } }], "predicates": [{ "equals": { "headers": { "Accept": "application/xml" } } }] }, { "responses": [{ "is": { "statusCode": 405 } }], "predicates": [{ "equals": { "method": "PUT" } }] }, { "responses": [{ "is": { "statusCode": 500 } }], "predicates": [{ "equals": { "method": "PUT" } }] } ]}
The first predicate is the most complex, and the request has to match all of the specified request fields. We have the option of putting multiple fields under one equals
predicate or splitting each one into a separate predicate in the array. In this example, all of the ones that share the default predicate parameters are together. For those, neither the case of the keys nor the values will affect the outcome. The body
predicate is treated separately. The text will be compared in a case-sensitive manner, after stripping away the regular expression!$
(an exclamation mark anchored to the end of the string).
The order of the query parameters and header fields does not matter.
POST /test?Second=2&First=1 HTTP/1.1Host: localhost:4545accept: text/plainhello, world!
HTTP/1.1 400 Bad RequestConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunked
The second stub matches if the header changes.
POST /test?Second=2&First=1 HTTP/1.1Host: localhost:4545Accept: application/xml"hello, world!"
HTTP/1.1 406 Not AcceptableConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunked
The third stub matches on a PUT
.
PUT /test?Second=2&First=1 HTTP/1.1Host: localhost:4545Accept: application/json"hello, world!"
HTTP/1.1 405 Method Not AllowedConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunked
The fourth stub will never run, since it matches the same requests as the third stub. mountebank always chooses the first stub that matches based on the order you add them to the stubs
array when creating the imposter.
Let's create an HTTP imposter with multiple stubs:
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4556, "protocol": "http", "stubs": [ { "responses": [{ "is": { "body": "first" } }], "predicates": [{ "deepEquals": { "query": {} } }] }, { "responses": [{ "is": { "body": "second" } }], "predicates": [{ "deepEquals": { "query": { "first": "1" } } }] }, { "responses": [{ "is": { "body": "third" } }], "predicates": [{ "deepEquals": { "query": { "first": "1", "second": "2" } } }] } ]}
The first predicate matches only a request without a querystring.
GET /test HTTP/1.1Host: localhost:4556
HTTP/1.1 200 OKConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunkedfirst
The second stub matches only if the exact querystring is sent.
GET /test?First=1 HTTP/1.1Host: localhost:4556
HTTP/1.1 200 OKConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunkedsecond
The third stub matches only if both query keys are sent.
GET /test?Second=2&First=1 HTTP/1.1Host: localhost:4556
HTTP/1.1 200 OKConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunkedthird
Any additional query parameters will trigger the default HTTP response.
GET /test?Second=2&First=1&Third=3 HTTP/1.1Host: localhost:4556
HTTP/1.1 200 OKConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunked
Let's create a binary TCP imposter with multiple stubs:
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4547, "protocol": "tcp", "mode": "binary", "stubs": [ { "responses": [{ "is": { "data": "Zmlyc3QgcmVzcG9uc2U=" } }], "predicates": [{ "contains": { "data": "AgM=" } }] }, { "responses": [{ "is": { "data": "c2Vjb25kIHJlc3BvbnNl" } }], "predicates": [{ "contains": { "data": "Bwg=" } }] }, { "responses": [{ "is": { "data": "dGhpcmQgcmVzcG9uc2U=" } }], "predicates": [{ "contains": { "data": "Bwg=" } }] } ]}
We're sending a base64-encoded version of four bytes: 0x1, 0x2, 0x3, and 0x4. Our first predicate is a base64 encoded version of 0x2 and 0x3. The response is a base64-encoded version of the string "first response":
echo 'AQIDBA==' | base64 --decode | nc localhost 4547
first response
Next we'll send 0x5, 0x6, 0x7, and 0x8, matching on a predicate encoding 0x7 and 0x8:
echo 'BQYHCA==' | base64 --decode | nc localhost 4547
second response
The third stub will never run, since it matches the same requests as the second stub. mountebank always chooses the first stub that matches based on the order you add them to the stubs
array when creating the imposter.
Let's create a text-based imposter with multiple stubs. Binary imposters won't see any interesting behavior difference with only startsWith
predicates:
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4548, "protocol": "tcp", "mode": "text", "stubs": [ { "responses": [{ "is": { "data": "first response" } }], "predicates": [{ "startsWith": { "data": "first" } }] }, { "responses": [{ "is": { "data": "second response" } }], "predicates": [{ "startsWith": { "data": "second" } }] }, { "responses": [{ "is": { "data": "third response" } }], "predicates": [{ "startsWith": { "data": "second" } }] } ]}
The match is not case-sensitive:
echo 'FIRST REQUEST' | nc localhost 4548
first response
The same is true for the second stub.
echo 'Second Request' | nc localhost 4548
second response
The third stub will never run, since it matches the same requests as the second stub. mountebank always chooses the first stub that matches based on the order you add them to the stubs
array when creating the imposter.
Let's create a binary-based imposter with multiple stubs:
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4549, "protocol": "tcp", "mode": "binary", "stubs": [ { "responses": [{ "is": { "data": "Zmlyc3QgcmVzcG9uc2U=" } }], "predicates": [{ "endsWith": { "data": "AwQ=" } }] }, { "responses": [{ "is": { "data": "c2Vjb25kIHJlc3BvbnNl" } }], "predicates": [{ "endsWith": { "data": "BQY=" } }] }, { "responses": [{ "is": { "data": "dGhpcmQgcmVzcG9uc2U=" } }], "predicates": [{ "endsWith": { "data": "BQY=" } }] } ]}
We'll use the command line base64
tool to decode the request to binary before sending to the imposter. We're sending a base64-encoded version of four bytes: 0x1, 0x2, 0x3, and 0x4. Our first predicate is a base64 encoded version of 0x3 and 0x4. The response is a base64-encoded version of the string "first response":
echo 'AQIDBA==' | base64 --decode | nc localhost 4549
first response
Next we'll send 0x1, 0x2, 0x4, 0x5, and 0x6, matching on a predicate encoding 0x5 and 0x6:
echo 'AQIDBAUG' | base64 --decode | nc localhost 4549
second response
The third stub will never run, since it matches the same requests as the second stub. mountebank always chooses the first stub that matches based on the order you add them to the stubs
array when creating the imposter.
Let's create a text-based imposter with multiple stubs. Binary imposters cannot use the matches
predicate.
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4550, "protocol": "tcp", "mode": "text", "stubs": [ { "responses": [{ "is": { "data": "first response" } }], "predicates": [{ "matches": { "data": "^first\\Wsecond" }, "caseSensitive": true }] }, { "responses": [{ "is": { "data": "second response" } }], "predicates": [{ "matches": { "data": "second\\s+request" } }] }, { "responses": [{ "is": { "data": "third response" } }], "predicates": [{ "matches": { "data": "second\\s+request" } }] } ]}
The first stub requires a case-sensitive match on a string starting with "first", followed by a non-word character, followed by "second":
echo 'first second' | nc localhost 4550
first response
The second stub is not case-sensitive.
echo 'Second Request' | nc localhost 4550
second response
The third stub will never run, since it matches the same requests as the second stub. mountebank always chooses the first stub that matches based on the order you add them to the stubs
array when creating the imposter.
The exists
predicate is primarily for object data types, like HTTP headers and query parameters. It works on string fields by simply returning true
if the exists
value is true
and the string if non-empty. Setting the exists
value to false
inverts the meaning.
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4551, "protocol": "http", "stubs": [ { "responses": [{ "is": { "body": "first response" } }], "predicates": [ { "exists": { "query": { "q": true, "search": false }, "headers": { "Accept": true, "X-Rate-Limit": false } } } ] }, { "responses": [{ "is": { "body": "second response" } }], "predicates": [ { "exists": { "method": true, "body": false } } ] }, { "responses": [{ "is": { "body": "third response" } }], "predicates": [ { "exists": { "body": true } } ] } ]}
The first stub matches if the querystring includes q
, but not if it includes search
, and if the headers includeAccept
, but not if they include X-Rate-Limit
.
GET /?q=mountebank HTTP/1.1Host: localhost:4551Accept: text/plain
HTTP/1.1 200 OKConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunkedfirst response
The second stub matches if the request method
is a non-empty string (always true
), and if the body
is empty.
GET / HTTP/1.1Host: localhost:4551
HTTP/1.1 200 OKConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunkedsecond response
The last stub matches if the body
is non-empty:
POST / HTTP/1.1Host: localhost:4551non-empty body
HTTP/1.1 200 OKConnection: closeDate: Thu, 09 Jan 2014 02:30:31 GMTTransfer-Encoding: chunkedthird response
The not
predicate negates its child predicate.
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4552, "protocol": "tcp", "mode": "text", "stubs": [ { "responses": [{ "is": { "data": "not test" } }], "predicates": [{ "not": { "equals": { "data": "test\n" } } }] }, { "responses": [{ "is": { "data": "test" } }], "predicates": [{ "equals": { "data": "test\n" } }] } ]}
The first stub matches if the is
sub-predicate does not match:
echo 'production' | nc localhost 4552
not test
As expected, the second stub matches if the is
sub-predicate does match:
echo 'test' | nc localhost 4552
test
The or
predicate matches if any of its sub-predicates match.
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4553, "protocol": "tcp", "mode": "text", "stubs": [ { "responses": [{ "is": { "data": "matches" } }], "predicates": [ { "or": [ { "startsWith": { "data": "start" } }, { "endsWith": { "data": "end\n" } }, { "contains": { "data": "middle" } } ] } ] } ]}
The stub matches if the first sub-predicate matches:
echo 'start data transmission' | nc localhost 4553
matches
The stub matches if the second sub-predicate matches:
echo 'data transmission end' | nc localhost 4553
matches
The stub matches if the last sub-predicate matches:
echo 'data middle transmission' | nc localhost 4553
matches
The stub does not match none of the sub-predicates match...
echo 'data transmission' | nc localhost 4553
...which yields an empty response:
The and
predicate matches if all of its sub-predicates match.
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4554, "protocol": "tcp", "mode": "text", "stubs": [ { "responses": [{ "is": { "data": "matches" } }], "predicates": [ { "and": [ { "startsWith": { "data": "start" } }, { "endsWith": { "data": "end\n" } }, { "contains": { "data": "middle" } } ] } ] } ]}
The first request matches all sub-predicates, triggering the stub response:
echo 'start middle end' | nc localhost 4554
matches
The stub matches two of the three sub-predicates, which is not enough to match the and
predicate.
The stub does not match none of the sub-predicates match...
echo 'start end' | nc localhost 4554
No response is sent.
The inject
predicate allows you to inject JavaScript to determine if the predicate should match or not. The JavaScript should be a function that accepts the request object (and optionally a logger) and returns true or false. See the injection page for details.
The execution will have access to a node.js environment. The following example uses node's Buffer
object to decode base64 to a byte array.
POST /imposters HTTP/1.1Host: localhost:11071Accept: application/jsonContent-Type: application/json{ "port": 4555, "protocol": "tcp", "mode": "binary", "stubs": [ { "responses": [{ "is": { "data": "Zmlyc3QgcmVzcG9uc2U=" } }], "predicates": [{ "inject": "function (request, logger) { logger.info('Inside injection'); return new Buffer(request.data, 'base64')[2] > 100; }" }] }, { "responses": [{ "is": { "data": "c2Vjb25kIHJlc3BvbnNl" } }], "predicates": [{ "inject": "function (request) { return new Buffer(request.data, 'base64')[2] <= 100; }" }] } ]}
The first stub matches if the third byte is greater than 100. The request we're sending is an encoding [99, 100, 101]:
echo 'Y2Rl' | base64 --decode | nc localhost 4555
first response
The logs will also show the injected log output. The second predicate has to match a request originating from localhost with the third byte less than or equal to 100. We're sending [98, 99, 100]
:
echo 'YmNk' | base64 --decode | nc localhost 4555
...giving the response:
second response
- mountebank之Predicates
- Predicates
- Predicates
- Comparison Predicates
- predicates 案例
- Dynamically Composing Expression Predicates
- predicates (from functional.h)
- Predicates of Foundation Framework
- Predicates 判断式
- Cocoa Predicates Classes
- LINQ to Entities: Combining Predicates(组合判断)
- 谓词函数predicates和仿函数functors
- 关于谓词函数predicates的介绍
- Effective STL 39 Make predicates pure functions
- 隐式转换在执行计划中对Access predicates 和Filter predicates 的影响
- Linq复杂查询 LINQ to Entities: Combining Predicates
- https://github.com/tmsmith/Dapper-Extensions/wiki/Predicates
- Dynamically Composing Expression Predicates (ef 多条件查询 相关)
- 2016SDAU课程练习三1007 Problem G
- OC中通过Class动态添加一个控制器,Swift中怎么实现?
- Fragment的setUserVisibleHint方法实现懒加载
- 算法之深度优先搜索和广度优先搜索
- ViewPager实现淘宝天面首页广告栏,支持左右滑动,自动滑动,带圆点指示器
- mountebank之Predicates
- 用iptables 做NAT代理上网 (内网内一台不能上网的机器通过能上网的机器作代理上网)
- Java的final关键字
- Codeforces Round 354 div2 676ABCDE
- 产品经理的工作职责
- 对Conten:”\20”、zoom:1及z-index: 1的理解
- 星号版的平行四边形
- 手把手教你使用CocoaPods管理你的iOS第三方开源类库
- poj 2828 Buy Tickets 线段树