Expand description
Declarative checks a request makes against the response it gets back.
A request file may carry an assertions block:
method: GET
url: https://httpbin.org/get
assertions:
status: 200
status_in: [200, 201, 204]
headers:
content-type: application/json # present, with this exact value
x-request-id: # present, value not checked
body_contains: '"url"'
body_matches: '^\{"url"'
elapsed_ms_under: 2000
json:
$.headers.Accept: application/json # bare value: equality
$.count: { greater_than: 5 } # operator object: comparison
$.id: { matches: '^[0-9a-f-]{36}$' } # operator object: regex
not:
status: 404Every key is optional and a request with no assertions block behaves
exactly as it did before this module existed.
Two things need a word up front, because they are not obvious from the schema alone:
json: disambiguates bare values from operators by shape. A path’s
expected value is read as an equality check unless it is a YAML mapping
with exactly one key drawn from greater_than, greater_than_or_equal,
less_than, less_than_or_equal, contains, length or matches, in
which case it is that operator instead. This means an equality check
against a genuine one-key object shaped like {greater_than: 5} is not
expressible — a real limitation, accepted because the alternative (a
separate block for operators) would make every path assertion say twice
which kind it is, and {greater_than: 5} as a literal expected value is
not a shape real APIs return. A multi-key object ({id: 1, name: ada})
is never ambiguous and is always equality, exactly as before.
There is deliberately no not_equal operator: not: {json: {$.count: 5}} already says “not equal to 5” precisely, and a dedicated operator
would only be a shorter spelling of the negation wrapper that already
exists — unlike greater_than/less_than, which are comparisons not:
cannot express at all (not: {json: {$.count: {greater_than: 5}}} means
<= 5, not < 5, and there is no operator-free way to write “less than
5” as a negation).
not: wraps a whole assertions block, not one assertion. It takes
the same keys as the top level (minus not itself — nesting not inside
not is a parse error, not a double negative) and negates each one
independently: not: {status: 404, body_contains: error} is “status is
not 404” and “body does not contain error”, not “status is 404 and
body contains error” negated as a pair. A wrapper was chosen over a
not_status / not_body_contains key for every assertion type because
it composes for free with whatever assertion kind is added next, rather
than doubling the schema’s key count every time one is.
A hard error — a malformed JSON path, an invalid regex (whether from
body_matches or a matches operator), a body that is not JSON, a JSON
path selecting zero or several values, a comparison or length operator
applied to a value of the wrong type — is not something not: can turn
into a pass. These are facts about the request or the file, not a
condition to be true or false, so not: {json: {$.a: {greater_than: 5}}}
against a body where $.a is a string still fails, the same way it would
unwrapped.
Evaluation never fails. Assertions::evaluate returns an
AssertionReport and no Result: everything that could go wrong — a body
that is not JSON, a JSON path that does not parse, a header that is not there
— is a failed assertion with a message, not an error in the surrounding
run. The response has already arrived by the time any of this happens, so
there is nothing left to abort; the only useful thing to do with a broken
expectation is to say precisely how it broke, next to the ones that held.
Nothing here decides an exit code. Evaluating and reporting is all this
module does; whether a failed assertion should fail the process is a
front-end decision, and today the answer is no. See the exit-code table in
sendra-cli.
Structs§
- Assertion
Report - Every assertion on one request, evaluated against one response, in a fixed
order: status,
status_in, headers,body_contains,body_matches,elapsed_ms_under, then JSON paths — with the entries of each map in sorted order — followed by the same order again for thenot:block, if there is one. Deterministic because the output is read by people and diffed by scripts, and neither is served by an order that depends on how aBTreeMaphappened to be filled. - Assertion
Result - One assertion, evaluated.
- Assertions
- The
assertionsblock of a request, exactly as it appears on disk. - NotAssertions
- The inner block of a
not:wrapper: every keyAssertionshas, exceptnotitself.
Enums§
- Assertion
Kind - Which kind of check produced a result, for a front-end that wants to group, filter or colour by kind rather than parse the rendered text.