pytest and respx: fake a Sume job that is queued, then completed
Test your Sume poll loop with respx: a queued, a processing and a completed response in order, with sleep injected so the test finishes in milliseconds.

To unit-test a Sume poll loop without the network, mock httpx with respx and give the route a list of responses: queued, then processing, then completed. Inject the sleep function so the test does not wait, and assert that the loop stops on terminal and honors next_poll_after_seconds.
The shapes come from Jobs and results: the status read carries terminal, sume_status (queued, processing, completed, failed, canceled) and next_poll_after_seconds, which is null once the job is terminal.
What does the test look like?
The function under test takes its client and its sleep, so both are easy to fake.
import httpx, respx
def poll(client, job_id, sleep):
while True:
d = client.get(f"/v1/jobs/{job_id}/status").json()["data"]
if d["terminal"]:
return d["sume_status"]
sleep(max(d["next_poll_after_seconds"] or 2, 2))
def body(status, terminal, wait):
return httpx.Response(200, json={"data": {"sume_status": status,
"terminal": terminal, "next_poll_after_seconds": wait}})
@respx.mock
def test_poll_stops_on_terminal():
route = respx.get("https://api.sume.com/v1/jobs/job_1/status")
route.side_effect = [body("queued", False, 5),
body("processing", False, 3),
body("completed", True, None)]
waits = []
with httpx.Client(base_url="https://api.sume.com") as c:
assert poll(c, "job_1", waits.append) == "completed"
assert waits == [5, 3] and route.call_count == 3Which cases deserve a test?
| Case | Fake response | Assert |
|---|---|---|
| Normal finish | queued, processing, completed | Stops on the third read, no extra call |
| Failed job | status failed, terminal true | Returns failed; no result read |
| Server asks for a longer gap | next_poll_after_seconds 30 | Sleep gets at least 30 |
| Transient 429 on a read | 429 with retry-after | Loop backs off and continues, run not abandoned |
| Terminal with null wait | next_poll_after_seconds null | No TypeError on max() |
What do people forget?
next_poll_after_secondsis null for terminal jobs; the loop above only reads it for non-terminal ones, but a naivemax(None, 2)raises.- Asserting on call count catches a loop that polls once too often, which in production is spend against your read budget.
- A fake 429 on a poll is a read failure, not a job failure. Errors and rate limits says to back off using
retry-after.
How do you test the deadline without waiting?
Pass the clock in as well as the sleep. A loop that takes now() and sleep() as parameters can be driven by a fake clock that jumps forward on every sleep, so a test for your poll deadline runs instantly and asserts the exact behavior you want: raise a timeout that carries the job id, and do not call cancel. Cancelling is a separate decision, because the cancel endpoint only succeeds before generation starts and answers 409 job_generation_already_started afterwards.
A second useful fake is a status read that returns failed with an error object. Assert that your code reads the failure from the job record rather than calling /result, which answers 409 job_not_completed for anything that is not completed.
Where does this leave production?
Pair the unit test with one real call against a development workspace. The mock proves your loop's logic; only a real job proves the prompt and the account.
Sources
Related posts
More in Developers
- Python 3.10 is end of life: a stdlib Sume webhook verifier
Python 3.10 has reached end of life. A standard-library verifier for Sume's signed webhooks that refuses an empty secret and accepts rotated signatures.
- Python 3.14.8 urllib credential fix: a stdlib Sume job poll script
Python 3.14.8 fixed urllib HTTPPasswordMgr handing credentials across schemes. Upgrade, and call Sume with an x-api-key header from a plain stdlib script.
- Log x-sume-request-id on every Sume call in Python
Capture the x-sume-request-id response header in a requests hook, attach it to each log line with logging.Filter and contextvars, and quote it to support.
- Railway webhook custom headers vs Sume's signed headers
Railway project webhooks now accept custom headers. Sume signs its callbacks with x-sume-webhook-timestamp and x-sume-webhook-signature; verify them in Python.
Written by Sume