Scenarios (Stateful FSM)¶
Scenarios let a stub respond differently depending on where a flow is in a state machine — a WireMock/Mountebank-style FSM. Each stub can require a state to be eligible and can transition the state after it responds.
Stub fields¶
| Field | Meaning |
|---|---|
scenarioName |
Names the scenario this stub belongs to. |
requiredScenarioState |
The stub is only eligible when the scenario is in this state. |
newScenarioState |
After the stub responds, the scenario transitions to this state. |
newScenarioState is optional. If a matched stub omits newScenarioState, the scenario remains in
its current state — no transition happens. That lets a stub gate on state without ever advancing
it, which is how you model a repeatable step in the middle of a flow.
The implicit initial state is Started. State is tracked per flow id (see
Spaces); by default that is the imposter port, so a single
imposter has one scenario timeline.
Example — pay-then-fulfil¶
The first call to /pay returns 402 and moves the scenario to paid; subsequent calls match the
second stub and return 200.
{
"port": 4602,
"protocol": "http",
"stubs": [
{
"scenarioName": "checkout",
"requiredScenarioState": "Started",
"newScenarioState": "paid",
"predicates": [{ "equals": { "path": "/pay" } }],
"responses": [{ "is": { "statusCode": 402, "body": "payment required" } }]
},
{
"scenarioName": "checkout",
"requiredScenarioState": "paid",
"predicates": [{ "equals": { "path": "/pay" } }],
"responses": [{ "is": { "statusCode": 200, "body": "fulfilled" } }]
}
]
}
curl -i http://localhost:4602/pay # 402 payment required (now in state "paid")
curl -i http://localhost:4602/pay # 200 fulfilled
Parallel callers, independent timelines¶
By default all callers share one scenario timeline (the flow id defaults to the imposter port). To
let two callers advance the same scenarioName independently, scope the flow id to a request
header — e.g. flowIdSource: "header:X-Mock-Space" — so each caller's X-Mock-Space value gets its
own state:
{
"port": 4602,
"protocol": "http",
"_rift": {
"flowState": { "flowIdSource": "header:X-Mock-Space" }
},
"stubs": [
{
"scenarioName": "checkout",
"requiredScenarioState": "Started",
"newScenarioState": "paid",
"predicates": [{ "equals": { "path": "/pay" } }],
"responses": [{ "is": { "statusCode": 402, "body": "payment required" } }]
},
{
"scenarioName": "checkout",
"requiredScenarioState": "paid",
"predicates": [{ "equals": { "path": "/pay" } }],
"responses": [{ "is": { "statusCode": 200, "body": "fulfilled" } }]
}
]
}
Alice and bob each drive the checkout scenario at their own pace — one being mid-flow doesn't
gate or advance the other's state:
curl -i -H 'X-Mock-Space: alice' http://localhost:4602/pay # 402 (alice: Started -> paid)
curl -i -H 'X-Mock-Space: bob' http://localhost:4602/pay # 402 (bob: Started -> paid, independent of alice)
curl -i -H 'X-Mock-Space: alice' http://localhost:4602/pay # 200 (alice already paid)
curl http://localhost:2525/imposters/4602/scenarios?flowId=bob
# -> {"flowId":"bob","scenarios":[{"name":"checkout","state":"paid"}]}
See Correlated Isolation (Spaces) for how flowIdSource is
resolved.
Arranging and inspecting state¶
Only scenarios declared on stubs are tracked; there is no upfront registration.
# List scenario states (optionally scoped to a flow with ?flowId=)
curl http://localhost:2525/imposters/4602/scenarios
# -> {"flowId":"4602","scenarios":[{"name":"checkout","state":"paid"}]}
# Force a scenario into a state (body flowId optional)
curl -X PUT http://localhost:2525/imposters/4602/scenarios/checkout/state \
-d '{"state":"paid"}'
# Reset every scenario in a flow back to "Started"
curl -X POST http://localhost:2525/imposters/4602/scenarios/reset -d '{}'
See the API Reference for the full endpoint contract.
A stub serving _rift.conditional behind a scenario gate re-stamps its Last-Modified on every
transition of that scenario, however it is made (newScenarioState, the scenarios or flow-state
admin API, _rift.stateOps, a script's ctx.state), so a polling client sees the switch. A
positive TTL running out on a scenario's key is the exception: no write happens, so nothing is
stamped. See
Conditional GET.