From Wave 5 — F-Q-04, F-Q-17, F-N-14. Severity: medium.
Coverage gap (F-Q-04)
`_list_all` (`kalshi/resources/_base.py:147-185` sync, `:260-291` async) hard-codes `max_pages: int = 1000` as a safety net for "server never repeats a cursor but never returns empty either". Cursor-loop detection is well-tested; the bare numeric cap isn't.
A refactor turning `for _ in range(max_pages)` into `while page.cursor` would compile, ship, and hang.
Test: mock an endpoint returning a fresh unique cursor on every call. Call `_list_all` with `max_pages=3`. Assert exactly 3 requests fired and 3 items yielded. Mirror sync/async.
Coverage gap (F-Q-17)
Empty-string cursor termination is asserted at the model level (`Page.has_next`) but no end-to-end test confirms `_list_all` stops after a page returns `{"cursor": ""}`. Borderline pinned — explicit assertion would catch a regression that forwards cursor unconditionally.
Test: mock a single-page response with `{"items": [{"id": "a"}], "cursor": ""}`. Assert `route.call_count == 1` after `list(_list_all(...))`.
Architecture gap (F-N-14)
The `max_pages` arg exists on `_list_all` but no public `*_all()` method forwards it. Callers can't raise the limit without subclassing the resource. For long histories (`markets.list_trades_all` over years, `historical.trades_all` with no `min_ts`), 1000 pages × default page size could exhaust the data well before the user expects, returning a silent partial.
Fix options:
- Expose `max_pages` as a kwarg on the public `*_all()` methods (same 1000 default).
- Raise a sentinel error when the cap is hit so users learn the iterator terminated artificially. Today the iterator just `break`s with no signal.
The two test gaps stay either way; exposing the kwarg adds a public API surface that should also have its own test.
From Wave 5 — F-Q-04, F-Q-17, F-N-14. Severity: medium.
Coverage gap (F-Q-04)
`_list_all` (`kalshi/resources/_base.py:147-185` sync, `:260-291` async) hard-codes `max_pages: int = 1000` as a safety net for "server never repeats a cursor but never returns empty either". Cursor-loop detection is well-tested; the bare numeric cap isn't.
A refactor turning `for _ in range(max_pages)` into `while page.cursor` would compile, ship, and hang.
Test: mock an endpoint returning a fresh unique cursor on every call. Call `_list_all` with `max_pages=3`. Assert exactly 3 requests fired and 3 items yielded. Mirror sync/async.
Coverage gap (F-Q-17)
Empty-string cursor termination is asserted at the model level (`Page.has_next`) but no end-to-end test confirms `_list_all` stops after a page returns `{"cursor": ""}`. Borderline pinned — explicit assertion would catch a regression that forwards cursor unconditionally.
Test: mock a single-page response with `{"items": [{"id": "a"}], "cursor": ""}`. Assert `route.call_count == 1` after `list(_list_all(...))`.
Architecture gap (F-N-14)
The `max_pages` arg exists on `_list_all` but no public `*_all()` method forwards it. Callers can't raise the limit without subclassing the resource. For long histories (`markets.list_trades_all` over years, `historical.trades_all` with no `min_ts`), 1000 pages × default page size could exhaust the data well before the user expects, returning a silent partial.
Fix options:
The two test gaps stay either way; exposing the kwarg adds a public API surface that should also have its own test.