Files
xiaoxia-saas/tests/unit/test_pagination.py
T
xiaoxia 5f17fd5faf
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
test(pagination): P3-1第十一波 通用分页器单测 52个 (#715)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-22 15:36:06 +08:00

338 lines
11 KiB
Python
Executable File

"""
pagination 通用分页器单元测试
覆盖:
- PaginationParams: 默认值/边界/校验/offset/limit
- PaginationMeta: from_params 各种边界场景
- PaginatedResponse: create 工厂方法
- paginate: 内存分页函数
"""
import pytest
from pydantic import ValidationError
from packages.application.common.pagination import (
PaginatedResponse,
PaginationMeta,
PaginationParams,
paginate,
)
# ============================================================
# PaginationParams
# ============================================================
class TestPaginationParamsDefaults:
"""默认值测试"""
def test_default_page_is_1(self):
params = PaginationParams()
assert params.page == 1
def test_default_page_size_is_20(self):
params = PaginationParams()
assert params.page_size == 20
def test_default_offset_is_0(self):
params = PaginationParams()
assert params.offset == 0
def test_default_limit_is_20(self):
params = PaginationParams()
assert params.limit == 20
class TestPaginationParamsValidation:
"""参数校验"""
@pytest.mark.parametrize("page", [1, 2, 100, 9999])
def test_valid_page_values(self, page):
params = PaginationParams(page=page)
assert params.page == page
def test_page_zero_raises(self):
with pytest.raises(ValidationError):
PaginationParams(page=0)
def test_page_negative_raises(self):
with pytest.raises(ValidationError):
PaginationParams(page=-1)
@pytest.mark.parametrize("page_size", [1, 20, 50, 100])
def test_valid_page_size_values(self, page_size):
params = PaginationParams(page_size=page_size)
assert params.page_size == page_size
def test_page_size_zero_raises(self):
with pytest.raises(ValidationError):
PaginationParams(page_size=0)
def test_page_size_negative_raises(self):
with pytest.raises(ValidationError):
PaginationParams(page_size=-5)
def test_page_size_over_100_raises(self):
with pytest.raises(ValidationError):
PaginationParams(page_size=101)
def test_invalid_page_type_raises(self):
with pytest.raises(ValidationError):
PaginationParams(page="abc")
def test_invalid_page_size_type_raises(self):
with pytest.raises(ValidationError):
PaginationParams(page_size="abc")
class TestPaginationParamsOffset:
"""offset 属性计算"""
def test_page_1_offset_0(self):
params = PaginationParams(page=1, page_size=20)
assert params.offset == 0
def test_page_2_offset_page_size(self):
params = PaginationParams(page=2, page_size=20)
assert params.offset == 20
def test_page_3_offset_2x_page_size(self):
params = PaginationParams(page=3, page_size=20)
assert params.offset == 40
def test_page_5_page_size_10_offset_40(self):
params = PaginationParams(page=5, page_size=10)
assert params.offset == 40
def test_page_1_page_size_100_offset_0(self):
params = PaginationParams(page=1, page_size=100)
assert params.offset == 0
class TestPaginationParamsLimit:
"""limit 属性"""
def test_limit_equals_page_size(self):
params = PaginationParams(page_size=20)
assert params.limit == 20
def test_limit_1(self):
params = PaginationParams(page_size=1)
assert params.limit == 1
def test_limit_100(self):
params = PaginationParams(page_size=100)
assert params.limit == 100
# ============================================================
# PaginationMeta.from_params
# ============================================================
class TestPaginationMetaFromParams:
"""from_params 工厂方法"""
def test_empty_total_zero(self):
params = PaginationParams(page=1, page_size=20)
meta = PaginationMeta.from_params(params, total=0)
assert meta.total == 0
assert meta.total_pages == 0
assert meta.has_next is False
assert meta.has_prev is False
def test_exactly_one_page(self):
params = PaginationParams(page=1, page_size=20)
meta = PaginationMeta.from_params(params, total=20)
assert meta.total_pages == 1
assert meta.has_next is False
assert meta.has_prev is False
def test_less_than_one_page(self):
params = PaginationParams(page=1, page_size=20)
meta = PaginationMeta.from_params(params, total=15)
assert meta.total_pages == 1
assert meta.has_next is False
assert meta.has_prev is False
def test_multiple_pages_first_page(self):
params = PaginationParams(page=1, page_size=20)
meta = PaginationMeta.from_params(params, total=50)
assert meta.total_pages == 3
assert meta.has_next is True
assert meta.has_prev is False
def test_multiple_pages_middle_page(self):
params = PaginationParams(page=2, page_size=20)
meta = PaginationMeta.from_params(params, total=50)
assert meta.total_pages == 3
assert meta.has_next is True
assert meta.has_prev is True
def test_multiple_pages_last_page(self):
params = PaginationParams(page=3, page_size=20)
meta = PaginationMeta.from_params(params, total=50)
assert meta.total_pages == 3
assert meta.has_next is False
assert meta.has_prev is True
def test_exact_division(self):
params = PaginationParams(page=2, page_size=20)
meta = PaginationMeta.from_params(params, total=40)
assert meta.total_pages == 2
assert meta.has_next is False
assert meta.has_prev is True
def test_non_exact_division_ceil(self):
params = PaginationParams(page=1, page_size=20)
meta = PaginationMeta.from_params(params, total=41)
assert meta.total_pages == 3
def test_total_1_page_size_20(self):
params = PaginationParams(page=1, page_size=20)
meta = PaginationMeta.from_params(params, total=1)
assert meta.total_pages == 1
assert meta.has_next is False
assert meta.has_prev is False
def test_page_beyond_total_pages(self):
params = PaginationParams(page=10, page_size=20)
meta = PaginationMeta.from_params(params, total=50)
assert meta.total_pages == 3
assert meta.has_next is False
assert meta.has_prev is True
def test_preserves_params_values(self):
params = PaginationParams(page=3, page_size=15)
meta = PaginationMeta.from_params(params, total=100)
assert meta.page == 3
assert meta.page_size == 15
assert meta.total == 100
# ============================================================
# PaginatedResponse.create
# ============================================================
class TestPaginatedResponseCreate:
"""create 工厂方法"""
def test_create_with_data(self):
params = PaginationParams(page=1, page_size=20)
data = [1, 2, 3]
response = PaginatedResponse.create(data, params, total=100)
assert response.data == data
assert response.pagination.total == 100
assert response.pagination.page == 1
assert response.pagination.page_size == 20
def test_create_with_empty_data(self):
params = PaginationParams(page=1, page_size=20)
response = PaginatedResponse.create([], params, total=0)
assert response.data == []
assert response.pagination.total == 0
assert response.pagination.total_pages == 0
def test_create_preserves_list_type(self):
params = PaginationParams(page=1, page_size=20)
data = ["a", "b", "c"]
response = PaginatedResponse.create(data, params, total=10)
assert response.data == ["a", "b", "c"]
assert len(response.data) == 3
# ============================================================
# paginate 函数
# ============================================================
class TestPaginateFunction:
"""内存分页函数"""
def test_empty_list(self):
params = PaginationParams(page=1, page_size=20)
result = paginate([], params)
assert result.data == []
assert result.pagination.total == 0
assert result.pagination.total_pages == 0
def test_first_page(self):
items = list(range(50))
params = PaginationParams(page=1, page_size=20)
result = paginate(items, params)
assert result.data == list(range(20))
assert result.pagination.total == 50
assert result.pagination.total_pages == 3
assert result.pagination.has_next is True
assert result.pagination.has_prev is False
def test_middle_page(self):
items = list(range(50))
params = PaginationParams(page=2, page_size=20)
result = paginate(items, params)
assert result.data == list(range(20, 40))
assert result.pagination.has_next is True
assert result.pagination.has_prev is True
def test_last_page(self):
items = list(range(50))
params = PaginationParams(page=3, page_size=20)
result = paginate(items, params)
assert result.data == list(range(40, 50))
assert len(result.data) == 10
assert result.pagination.has_next is False
assert result.pagination.has_prev is True
def test_page_beyond_total(self):
items = list(range(25))
params = PaginationParams(page=10, page_size=20)
result = paginate(items, params)
assert result.data == []
assert result.pagination.total == 25
assert result.pagination.total_pages == 2
def test_page_size_larger_than_total(self):
items = list(range(5))
params = PaginationParams(page=1, page_size=20)
result = paginate(items, params)
assert result.data == items
assert result.pagination.total_pages == 1
assert result.pagination.has_next is False
def test_single_item(self):
items = [42]
params = PaginationParams(page=1, page_size=20)
result = paginate(items, params)
assert result.data == [42]
assert result.pagination.total == 1
def test_page_size_1(self):
items = list(range(5))
params = PaginationParams(page=3, page_size=1)
result = paginate(items, params)
assert result.data == [2]
assert result.pagination.total_pages == 5
def test_exact_page_size(self):
items = list(range(40))
params = PaginationParams(page=2, page_size=20)
result = paginate(items, params)
assert result.data == list(range(20, 40))
assert result.pagination.total_pages == 2
assert result.pagination.has_next is False
def test_string_items(self):
items = ["a", "b", "c", "d", "e"]
params = PaginationParams(page=2, page_size=2)
result = paginate(items, params)
assert result.data == ["c", "d"]
assert result.pagination.total == 5
def test_does_not_mutate_original_list(self):
items = list(range(10))
original = items.copy()
params = PaginationParams(page=1, page_size=3)
paginate(items, params)
assert items == original