From Wave 5 test-coverage audit — F-Q-01, F-Q-02, F-Q-03. Two high-severity gaps + one medium.
F-Q-01 — `Retry-After` cap to `retry_max_delay` is never tested (HIGH)
`_base_client.py:165-166` (sync) and `:278-279` (async) apply `min(error.retry_after, config.retry_max_delay)` so a hostile/misconfigured server returning `Retry-After: 86400` can't park the SDK for a day. `test_429_rate_limit` only checks the error mapper, not the transport's clamp inside the retry loop.
A regression that drops the `min(...)` or flips arg order would silently introduce unbounded sleeps. The whole point of `retry_max_delay` is unverified.
Test: mock a 429 with `Retry-After: "9999"` and `retry_max_delay=0.05`, then a 200. Patch `time.sleep`/`asyncio.sleep` with a recording stub. Assert recorded delay == 0.05, not 9999. Mirror sync/async.
F-Q-02 — HTTP-date `Retry-After` fallback to computed backoff is untested (MEDIUM)
`_map_error` catches `ValueError` on `float(retry_after)` and falls back to computed backoff (comment says "HTTP-date format"). No test passes a date string and confirms the SDK still retries.
Most upstream gateways (Cloudflare, AWS ALB) emit HTTP-date `Retry-After`. A regression that re-raised on `ValueError` would convert every rate-limit retry into a hard failure in front of those gateways.
Test: mock 429 with `Retry-After: "Wed, 21 Oct 2026 07:28:00 GMT"`. Assert (a) `_map_error` returns `KalshiRateLimitError` with `retry_after is None`, (b) the transport still retries via exponential backoff and eventually gets the 200.
F-Q-03 — `httpx.TimeoutException` retry path is unexercised (HIGH)
`_base_client.py:135-142` (sync) and `:248-255` (async) catch `TimeoutException` and retry on idempotent methods only. Zero hits for "TimeoutException" in `tests/`.
Read timeouts are routine. A refactor that converted the second `except` into `raise` would silently break list/get paths on flaky networks; POST/DELETE timeouts would suddenly trigger spurious retries (the `method.upper() in RETRYABLE_METHODS` safety check is also untested).
Test: `respx.mock` with `side_effect=httpx.TimeoutException("read timed out")` then a 200 — assert GET retries and succeeds; assert POST raises wrapped `KalshiError` on first timeout with `route.call_count == 1`. Mirror sync/async.
Related
[Issue #96 Retry-After validation] should add tests for negative/NaN as well — but that's a separate code change. This issue is purely the test-coverage gap on existing code.
From Wave 5 test-coverage audit — F-Q-01, F-Q-02, F-Q-03. Two high-severity gaps + one medium.
F-Q-01 — `Retry-After` cap to `retry_max_delay` is never tested (HIGH)
`_base_client.py:165-166` (sync) and `:278-279` (async) apply `min(error.retry_after, config.retry_max_delay)` so a hostile/misconfigured server returning `Retry-After: 86400` can't park the SDK for a day. `test_429_rate_limit` only checks the error mapper, not the transport's clamp inside the retry loop.
A regression that drops the `min(...)` or flips arg order would silently introduce unbounded sleeps. The whole point of `retry_max_delay` is unverified.
Test: mock a 429 with `Retry-After: "9999"` and `retry_max_delay=0.05`, then a 200. Patch `time.sleep`/`asyncio.sleep` with a recording stub. Assert recorded delay == 0.05, not 9999. Mirror sync/async.
F-Q-02 — HTTP-date `Retry-After` fallback to computed backoff is untested (MEDIUM)
`_map_error` catches `ValueError` on `float(retry_after)` and falls back to computed backoff (comment says "HTTP-date format"). No test passes a date string and confirms the SDK still retries.
Most upstream gateways (Cloudflare, AWS ALB) emit HTTP-date `Retry-After`. A regression that re-raised on `ValueError` would convert every rate-limit retry into a hard failure in front of those gateways.
Test: mock 429 with `Retry-After: "Wed, 21 Oct 2026 07:28:00 GMT"`. Assert (a) `_map_error` returns `KalshiRateLimitError` with `retry_after is None`, (b) the transport still retries via exponential backoff and eventually gets the 200.
F-Q-03 — `httpx.TimeoutException` retry path is unexercised (HIGH)
`_base_client.py:135-142` (sync) and `:248-255` (async) catch `TimeoutException` and retry on idempotent methods only. Zero hits for "TimeoutException" in `tests/`.
Read timeouts are routine. A refactor that converted the second `except` into `raise` would silently break list/get paths on flaky networks; POST/DELETE timeouts would suddenly trigger spurious retries (the `method.upper() in RETRYABLE_METHODS` safety check is also untested).
Test: `respx.mock` with `side_effect=httpx.TimeoutException("read timed out")` then a 200 — assert GET retries and succeeds; assert POST raises wrapped `KalshiError` on first timeout with `route.call_count == 1`. Mirror sync/async.
Related
[Issue #96 Retry-After validation] should add tests for negative/NaN as well — but that's a separate code change. This issue is purely the test-coverage gap on existing code.