Unit test a transcription retry loop with a fake 429 in Python
Test your Sume STT retry code without calling the API: inject the POST and sleep, return a 429 with retry-after, and assert the same key is sent twice.

You can test a Sume transcription retry loop without the network by passing the HTTP call and the sleep function in as arguments. A fake post returns (429, {"retry-after": "2"}) and then (202, {}); the test asserts that the loop slept 2.0 seconds and sent the same Idempotency-Key both times. Those two assertions are the contract in Errors and rate limits and Jobs and results: honor retry-after, and retry with the same key so no second paid job is created. Read 2026-10-06.
What to assert
A retry loop has three behaviors worth pinning down. Anything beyond that is testing the standard library.
| Behavior | Assertion |
|---|---|
| Waits as told | The sleep argument equals the retry-after header |
| Same intent, same key | Every attempt sends the identical key |
| Bounded | After tries attempts the function returns None |
The code and its tests
Save it as ut2.py and run python -m unittest ut2. Both tests pass in a few milliseconds because nothing sleeps.
import time, unittest
def submit_with_retry(post, key, sleep=time.sleep, tries=5):
for _ in range(tries):
status, headers = post(key)
if status == 429:
sleep(float(headers.get("retry-after", "5")))
continue
return status
class RetryTest(unittest.TestCase):
def test_retry_keeps_the_key(self):
keys, naps = [], []
replies = iter([(429, {"retry-after": "2"}), (202, {})])
def post(key):
keys.append(key)
return next(replies)
self.assertEqual(submit_with_retry(post, "stt-1", sleep=naps.append), 202)
self.assertEqual((keys, naps), (["stt-1", "stt-1"], [2.0]))
def test_gives_up(self):
post = lambda key: (429, {})
self.assertIsNone(submit_with_retry(post, "k", sleep=lambda s: None, tries=3))Wiring the real call in
In production, pass a post that wraps requests.post and returns (response.status_code, response.headers). The loop does not care where the numbers come from, which is what makes it testable.
Sources
Related posts
More in Developers
- Unity editor tool: generate an AI video clip with UnityWebRequest
A Unity coroutine posts a Wan 3.0 job to Sume, polls /v1/jobs/{id}/status with next_poll_after_seconds and downloads the MP4 with DownloadHandlerFile.
- Uptime monitor for the Sume API: probe GET /v1/me, spend reads
A 30 second probe of GET /v1/me costs two reads a minute against a 4,800 read Free budget. Python probe and a status table for 401, 429 and 5xx.
- What did one transcription job cost? GET /v1/usage by job_id
Read GET /v1/usage?job_id= and sum data.summary.debited_usd_micros to see what one Sume STT job really cost. Holds and refunds are not counted as spend.
- User closed the tab mid-render: cancel the Sume job or let it finish?
Cancel works only before generation starts; after that you get 409 job_generation_already_started and the job bills. A tested tab-close handler.
Written by Sume