# camel-integration-test
> Scenario-tier test support for rust-camel: `.test.yaml` documents that declare a `scenario:` section
Owns the scenario document model and parser behind the integration tier
of `camel test` (ADR-0069). A scenario document runs one action
vocabulary: `send`, `receive`, `sleep`, and `validate`. A `send` takes
an optional `method` HTTP token, uppercased at load. An invalid token
fails doc validation with exit 2. A `send` with a body defaults to
`POST`. A bodyless `send` defaults to `GET`. The parser bans the
unit-tier keys (`inputs`, `expects`, `intercepts`) at load time, and
it rejects `env` keys that collide with a declared `bindVar`. The
harness provisions each `http:` partner on `127.0.0.1:0` and folds the
partner's `bindVar` into a layered environment. Config and route
`${env:}` placeholders resolve through that environment, never the
process environment. Every action prints a PASS or FAIL row. Exit codes
follow the ADR-0069 taxonomy: 0 when all actions pass, 1 on a verdict
failure, 2 on a parse, boot, or apparatus failure.
## Usage
A minimal scenario document (`orders.test.yaml`):
```yaml
routeFiles:
- routes/bridge.yaml
scenario:
- send:
to: direct:start
method: PUT
body: order-payload-7f3a
- receive:
from:
endpoint: http://127.0.0.1:0/orders
provisioning: harness
bindVar: PARTNER
deadline: 2s
extract:
body: body
- validate:
target: { lastReceived: http://127.0.0.1:0/orders }
expectation: order-payload-7f3a
```
The partner endpoint binds on the harness and injects
`PARTNER=http://127.0.0.1:<bound>` into the layered environment, so the
route's `${env:PARTNER}` reaches the local listener. Run the document
with the CLI; the `http:` partner adapter rides the `integration-http`
Cargo feature, default-on in `camel-cli` since 2026-09-05:
```sh
cargo run -p camel-cli -- test --integration orders.test.yaml
```
## `partners:` section
A `partners:` section scripts how a harness partner answers requests.
It is a map. Each key is the exact declared endpoint string, the `:0`
URI as written in the scenario. The value is a sequence of script
entries.
Each entry carries optional `method` and `path` matchers. A request
matches an entry when its method and path match. An entry with no
`method` or `path` matches any request.
An entry declares exactly one of `response` or `fault: close`. The
`response` holds optional `status` (100-599), `headers`, and `body`. The
`fault: close` drops the connection without an HTTP response. The
request is still recorded before the fault.
An entry may also declare `times` and `delay`. The `times` is an
integer of 1 or more. It defaults to 1. An entry with no `times` keeps
the shipped serve-once behavior. The entry serves its first `times`
matching requests, then it is spent. The `delay` is a humantime
string. The harness holds for the delay before it serves the response
or commits the fault.
The harness serves the first unspent matching entry in order. A
request no entry matches serves status 500 with an empty body. A
document with no `partners:` section is permissive: every request
gets status 200 with an empty body.
For example, a timed delayed response entry can precede a fault entry
for the same request:
```yaml
partners:
http://127.0.0.1:0/orders:
- method: PUT
path: /orders
times: 2
delay: 100ms
response:
status: 201
body: A
- method: PUT
path: /orders
fault: close
```
The first entry serves the first two matching requests. Each response
arrives after the delay with status 201 and body `A`. The entry is
then spent. The third matching request hits the fault entry. Its
connection drops without an HTTP response.
Every `partners:` key must equal a declared harness `http` endpoint
reference. A key that matches no wired reference fails load with a
`doc-validation` error, exit 2, naming the key. The check runs before
any partner binds. A typo of a real key, for example `http://127.0.0.1:0/order` for
`:0/orders`, fails here. It never falls silently to permissive.
## `validate` targets
A `validate` action asserts against one of three targets, chosen by the
single key of its `target` map:
- `lastReceived`: the last message a `receive` action collected on that
endpoint. The expectation is a subset match, as in the usage example
above.
- `variable`: a scenario variable set by an earlier `extract`. Variable
existence is checked at run time. The expectation is a subset match
against the variable's value.
- `partner`: the recorded-request count of a harness partner, described
next.
A `partner` target asserts the recorded-request
count of a harness partner. The expectation holds `count`, an exact
non-negative integer, plus optional `method` and `path` filters. The
`method` filter compares ASCII-case-insensitively. The `path` filter
compares the path-and-query exactly. The count counts only the recorded
requests that pass both filters.
Without a `deadline`, the assertion reads one immediate snapshot. With a
`deadline`, it polls a fresh snapshot every 100 ms until the filtered
count equals the expectation or the deadline passes; the final snapshot
then decides. Arrivals only add, so a count above the expectation fails
at every snapshot and never settles back.
Partner expectations are exact-count, not subset like message
expectations: the count must equal the filtered arrivals, never a lower
bound.
## `${name}` interpolation
Scenario strings interpolate `${name}`. The surface covers three
places:
- endpoint strings in `send` and `receive`,
- body string leaves,
- header values.
Substitution is string-only. A string leaf with no placeholder stays
as is. Raw substitution applies, with no percent-encoding. An unset
variable at send time fails `scenario-var-unresolved`, exit 1, naming
the variable. Exit 1 is a verdict failure, not a parse error. In CI, a
document that fails this way is a failed test run, not a harness
error.
`$${` escapes a literal `${`. The escape applies to body leaves and
header values too. For example, a JSON body leaf that must reach the
wire as `${literal}` is written `$${literal}`.
The `receive` resolves by the interpolated authority. The path and
query need not match the `send`. A receive declared as
`http://${PARTNER}/orders` finds the roundtrip a map-form send parked.
## Minimal route source
An integration document may declare `routeFiles: []`. The empty list is
the minimal route source: no routes boot, and the scenario drives only
harness partners.
## Two layers, one name: `PARTNER`
The same name can carry two forms in one run.
| Layer | Form | Usage |
|-------|------|-------|
| scenario variable | `host:port` | `http://${PARTNER}/orders` |
| route env | `http://host:port` | `${env:PARTNER}` |
One-line rule: scenario = authority, route env = full URI. `${env:}`
deliberately does not resolve in scenario strings.
The [Testing chapter](../../docs/src/testing/index.md) documents the
full action grammar, the partner adapters, and the exit contract.
`examples/integration-testing/` is a runnable example. The
`partner-retry-route.test.yaml` pair there runs a real retrying route
against a faulted partner and asserts the two wire attempts.
## Related crates
- **camel-cli**: `camel test`, which parses and runs the documents
- **camel-bundles**: the boot cascade the scenario boot composes (ADR-0069 section 10)
- **camel-test**: unit-tier harness; the scenario tier never depends on it (ADR-0055)