From ba8ce362d7da250b488528cf0cb49dd766f58b23 Mon Sep 17 00:00:00 2001 From: Loi Nguyen Date: Wed, 22 Jul 2026 08:11:14 +0700 Subject: [PATCH] fix(api): PaginatedResult.pages should be 0 for an empty result set (#39229) Co-authored-by: Loi Nguyen <1948922+lntutor@users.noreply.github.com> Co-authored-by: Claude Fable 5 --- api/libs/pagination.py | 4 +- api/tests/unit_tests/libs/test_pagination.py | 52 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 api/tests/unit_tests/libs/test_pagination.py diff --git a/api/libs/pagination.py b/api/libs/pagination.py index a30ddc68d70..c6220fbc13e 100644 --- a/api/libs/pagination.py +++ b/api/libs/pagination.py @@ -23,9 +23,9 @@ class PaginatedResult[T]: @property def pages(self) -> int: - if self.per_page == 0: + if self.total == 0 or self.per_page == 0: return 0 - return max(1, math.ceil(self.total / self.per_page)) + return math.ceil(self.total / self.per_page) @property def has_next(self) -> bool: diff --git a/api/tests/unit_tests/libs/test_pagination.py b/api/tests/unit_tests/libs/test_pagination.py new file mode 100644 index 00000000000..0034ca20fa8 --- /dev/null +++ b/api/tests/unit_tests/libs/test_pagination.py @@ -0,0 +1,52 @@ +import math + +import pytest + +from libs.pagination import PaginatedResult + + +class TestPaginatedResultPages: + def test_pages_is_zero_for_empty_result(self): + # Parity with Flask-SQLAlchemy's Pagination.pages, which PaginatedResult + # was introduced to replace as a drop-in (#38280): an empty result set + # has zero pages, not one. + result = PaginatedResult(items=[], total=0, page=1, per_page=20) + assert result.pages == 0 + + def test_has_next_is_false_for_empty_result(self): + result = PaginatedResult(items=[], total=0, page=1, per_page=20) + assert result.has_next is False + + @pytest.mark.parametrize( + ("total", "per_page", "expected_pages"), + [ + (1, 20, 1), + (20, 20, 1), + (21, 20, 2), + (40, 20, 2), + (41, 20, 3), + ], + ) + def test_pages_for_non_empty_result(self, total, per_page, expected_pages): + result = PaginatedResult(items=[object()], total=total, page=1, per_page=per_page) + assert result.pages == expected_pages == math.ceil(total / per_page) + + def test_pages_is_zero_when_per_page_is_zero(self): + result = PaginatedResult(items=[], total=0, page=1, per_page=0) + assert result.pages == 0 + + +class TestPaginatedResultHasNext: + def test_has_next_true_when_more_pages_remain(self): + result = PaginatedResult(items=[object()], total=41, page=1, per_page=20) + assert result.has_next is True + + def test_has_next_false_on_last_page(self): + result = PaginatedResult(items=[object()], total=41, page=3, per_page=20) + assert result.has_next is False + + +def test_paginated_result_is_iterable(): + items = [1, 2, 3] + result = PaginatedResult(items=items, total=3, page=1, per_page=20) + assert list(result) == items