WireMock scenarios: fake a Sume job going queued to completed
Use one WireMock scenario per job so GET /v1/jobs/{id}/status answers queued, processing, then completed, and test the poll loop without spending.

Use one WireMock scenario per job: a scenario is a state machine, so the same GET /v1/jobs/{id}/status URL can answer queued on the first poll, processing on the second and completed on the third. That is the shape of a real Sume job, and it lets you test your poll loop's stop condition without spending a cent.
The scenario fields (scenarioName, requiredScenarioState, newScenarioState) are from WireMock's stateful behaviour page (read 2026-10-11). The status envelope is from the OpenAPI schema and Jobs and results. Point your client at the mock with its base URL: the TypeScript SDK's createSumeClient takes a baseUrl option (default https://api.sume.com).
Which three stubs describe a Sume job?
Every stub matches the same request and differs only in the state it requires and the state it moves to. The booleans are what a correct loop reads, so put the real values in each stub.
The schema marks many more fields as required than the stub below shows: request_id, status_url, result_url, events_url, cancel_url, recommended_poll_interval_seconds, retry_after_seconds, queue, queue_position and logs_available. Add them when your client is generated or validated from the OpenAPI file, or your tests will pass on a body the real API never sends. Two details are easy to get wrong. next_poll_after_seconds is null on terminal jobs, not zero, and cancel_url is null once generation has started and after any terminal state. Write a stub with those nulls, since a loop that does arithmetic on the delay will throw on the last poll.
| Required state | Moves to | `sume_status` | `terminal` | `result_ready` | `next_action` |
|---|---|---|---|---|---|
Started | processing | queued | false | false | poll_status |
processing | done | processing | false | false | poll_status |
done | (stays) | completed | true | true | fetch_result |
What does one stub look like?
This is the first mapping. The other two copy it with the new states and fields from the table. The status field is the queue-shaped twin of sume_status (IN_QUEUE, IN_PROGRESS, COMPLETED), and the schema says the two always agree, so keep them consistent in every stub.
{
"scenarioName": "sume-job-job_test_1",
"requiredScenarioState": "Started",
"newScenarioState": "processing",
"request": { "method": "GET", "url": "/v1/jobs/job_test_1/status" },
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"jsonBody": { "data": {
"job_id": "job_test_1",
"status": "IN_QUEUE",
"sume_status": "queued",
"terminal": false,
"result_ready": false,
"cancelable": true,
"next_action": "poll_status",
"next_poll_after_seconds": 1
} }
}
}How do I stub the failure paths?
Success is the easy half. Add a second scenario for a job that ends in failed, with terminal: true, result_ready: false and next_action: "inspect_events", which is what the schema documents for failed and canceled jobs. Then stub GET /v1/jobs/{id}/result with status 409 and an error envelope whose code is job_not_completed, because the API answers that for any job that is not completed. A client that fetches the result on terminal instead of on result_ready will fail that test, which is the point.
Also stub the in-between: a 429 with rate_limited and a retry-after header on one poll, so you see the loop sleep instead of crash. Sume's errors page says to use retry-after when present, and reads have their own, larger budget than writes, so a throttled status poll should never lead to a resubmit.
What can a mock not tell you?
Reset between tests with resetAllScenarios() or POST /__admin/scenarios/reset, otherwise the second test starts in done and passes for the wrong reason. Each test should also use its own job id, so scenarios do not share state. Name the scenario after the job id, as the stub above does, and a stale scenario from another test cannot leak in. In CI, start WireMock as a container on a fixed port and pass its address as the SDK baseUrl, so no code under test needs a mock-specific branch.
A stub only proves your parsing. It cannot show real queue depth, real next_poll_after_seconds values, plan limits or the exact wire format of a new field. Keep one live smoke test, on the cheapest job your plan allows, in a scheduled pipeline rather than on every commit, and check Prism if you want response bodies generated from the OpenAPI file rather than written by hand.
Sources
Related posts
More in Developers
- Word timestamps for an Eleven v4 voice: the router says 400, use STT
The Sume TTS Router rejects timestamps on eleven-* ids. Get word timings by running the finished narration through Sume STT for one cent a minute.
- xAI video API polling: pending, done, failed vs Sume job states
xAI's video API returns a request_id and three states. Here is how they map onto Sume's five /v1/videos statuses, with a runnable Python polling loop.
- Which MCP server lets Claude Code or Cursor generate video and images?
MCP servers that let Claude Code and Cursor make video and images: Sume, fal, Replicate, Runway, Higgsfield. Endpoints, sign-in, billing, setup.
- Idempotency keys for AI video APIs: retry without paying twice
An idempotency key makes a retried create return the original run or job instead of a second paid one. How Sume's Idempotency-Key works on each API.
Written by Sume