diff --git a/src/smallestai/atoms/helpers/_envelope.py b/src/smallestai/atoms/helpers/_envelope.py index 57f6ca32..e8950d3c 100644 --- a/src/smallestai/atoms/helpers/_envelope.py +++ b/src/smallestai/atoms/helpers/_envelope.py @@ -46,6 +46,15 @@ class Page: has_more: Optional[bool] = None +def _first_present(*values: Any) -> Any: + """First value the server actually sent, treating 0 and False as sent. + + `a or b` would skip a real 0, so a page the server honestly reports as empty + came back with total_count None instead of 0. + """ + return next((value for value in values if value is not None), None) + + def _get(obj: Any, name: str) -> Any: """Attribute- or key-based access (works for pydantic models and dicts).""" if obj is None: @@ -83,18 +92,16 @@ def as_page(response: Any) -> Page: if items is not None: return Page( items=list(items), - total_count=( - _get(data, "total_count") - or _get(data, "total") - or _get(data, "count") - or _get(data, "total_campaign_count") - or _get(pagination, "total") - or _get(pagination, "total_count") - ), - total_pages=_get(data, "total_pages") or _get(pagination, "total_pages"), - has_more=( - _get(data, "has_more") if _get(data, "has_more") is not None else _get(pagination, "has_more") + total_count=_first_present( + _get(data, "total_count"), + _get(data, "total"), + _get(data, "count"), + _get(data, "total_campaign_count"), + _get(pagination, "total"), + _get(pagination, "total_count"), ), + total_pages=_first_present(_get(data, "total_pages"), _get(pagination, "total_pages")), + has_more=_first_present(_get(data, "has_more"), _get(pagination, "has_more")), ) # Unknown shape — return the payload as a single-item page rather than guessing. diff --git a/tests/custom/test_as_page_envelope.py b/tests/custom/test_as_page_envelope.py index a86f5788..0994b50a 100644 --- a/tests/custom/test_as_page_envelope.py +++ b/tests/custom/test_as_page_envelope.py @@ -49,3 +49,26 @@ def test_campaigns_total_campaign_count() -> None: def test_nested_pagination() -> None: pg = as_page(_Resp({"logs": [1, 2], "pagination": {"total": 9, "hasMore": True}})) assert pg.items == [1, 2] and pg.total_count == 9 + + +def test_an_honestly_empty_page_keeps_its_zeros() -> None: + # `a or b` chaining skipped a real 0, so a page the server reported as empty came + # back as total_count None, which reads as "the server did not say". + pg = as_page(_Resp({"agents": [], "total_count": 0, "total_pages": 0, "has_more": False})) + assert pg.items == [] + assert pg.total_count == 0 + assert pg.total_pages == 0 + assert pg.has_more is False + + +def test_zeros_from_nested_pagination_are_kept_too() -> None: + pg = as_page(_Resp({"agents": [], "pagination": {"total": 0, "total_pages": 0, "has_more": False}})) + assert pg.total_count == 0 + assert pg.total_pages == 0 + assert pg.has_more is False + + +def test_a_flat_zero_still_wins_over_a_nested_value() -> None: + # Precedence is unchanged: the flat field is read first whether or not it is truthy. + pg = as_page(_Resp({"agents": [], "total_count": 0, "pagination": {"total": 7}})) + assert pg.total_count == 0