7e505a04c7
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
245 lines
7.4 KiB
Python
Executable File
245 lines
7.4 KiB
Python
Executable File
"""通用分页器单元测试."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from packages.application.common.pagination import (
|
|
PaginatedResponse,
|
|
PaginationMeta,
|
|
PaginationParams,
|
|
paginate,
|
|
)
|
|
|
|
|
|
class TestPaginationParams:
|
|
"""PaginationParams 测试"""
|
|
|
|
def test_default_values(self):
|
|
"""默认值正确"""
|
|
params = PaginationParams()
|
|
assert params.page == 1
|
|
assert params.page_size == 20
|
|
|
|
def test_offset_first_page(self):
|
|
"""第一页 offset 为 0"""
|
|
params = PaginationParams(page=1, page_size=20)
|
|
assert params.offset == 0
|
|
|
|
def test_offset_second_page(self):
|
|
"""第二页 offset 计算正确"""
|
|
params = PaginationParams(page=2, page_size=20)
|
|
assert params.offset == 20
|
|
|
|
def test_offset_custom_page_size(self):
|
|
"""自定义 page_size 的 offset"""
|
|
params = PaginationParams(page=3, page_size=10)
|
|
assert params.offset == 20
|
|
|
|
def test_limit_equals_page_size(self):
|
|
"""limit 等于 page_size"""
|
|
params = PaginationParams(page_size=50)
|
|
assert params.limit == 50
|
|
|
|
def test_page_must_be_at_least_1(self):
|
|
"""page 不能小于 1"""
|
|
with pytest.raises(ValidationError):
|
|
PaginationParams(page=0)
|
|
|
|
def test_page_negative_raises(self):
|
|
"""page 不能为负数"""
|
|
with pytest.raises(ValidationError):
|
|
PaginationParams(page=-1)
|
|
|
|
def test_page_size_must_be_at_least_1(self):
|
|
"""page_size 不能小于 1"""
|
|
with pytest.raises(ValidationError):
|
|
PaginationParams(page_size=0)
|
|
|
|
def test_page_size_max_100(self):
|
|
"""page_size 最大 100"""
|
|
with pytest.raises(ValidationError):
|
|
PaginationParams(page_size=101)
|
|
|
|
def test_page_size_100_is_valid(self):
|
|
"""page_size=100 是合法的"""
|
|
params = PaginationParams(page_size=100)
|
|
assert params.page_size == 100
|
|
|
|
|
|
class TestPaginationMeta:
|
|
"""PaginationMeta 测试"""
|
|
|
|
def test_from_params_first_page(self):
|
|
"""第一页元数据"""
|
|
params = PaginationParams(page=1, page_size=10)
|
|
meta = PaginationMeta.from_params(params, total=25)
|
|
|
|
assert meta.page == 1
|
|
assert meta.page_size == 10
|
|
assert meta.total == 25
|
|
assert meta.total_pages == 3
|
|
assert meta.has_next is True
|
|
assert meta.has_prev is False
|
|
|
|
def test_from_params_last_page(self):
|
|
"""最后一页元数据"""
|
|
params = PaginationParams(page=3, page_size=10)
|
|
meta = PaginationMeta.from_params(params, total=25)
|
|
|
|
assert meta.page == 3
|
|
assert meta.total_pages == 3
|
|
assert meta.has_next is False
|
|
assert meta.has_prev is True
|
|
|
|
def test_from_params_middle_page(self):
|
|
"""中间页元数据"""
|
|
params = PaginationParams(page=2, page_size=10)
|
|
meta = PaginationMeta.from_params(params, total=50)
|
|
|
|
assert meta.page == 2
|
|
assert meta.total_pages == 5
|
|
assert meta.has_next is True
|
|
assert meta.has_prev is True
|
|
|
|
def test_from_params_zero_total(self):
|
|
"""总数为 0 时"""
|
|
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_from_params_exact_multiple(self):
|
|
"""总数刚好是 page_size 的整数倍"""
|
|
params = PaginationParams(page=1, page_size=10)
|
|
meta = PaginationMeta.from_params(params, total=30)
|
|
|
|
assert meta.total_pages == 3
|
|
|
|
def test_from_params_single_page(self):
|
|
"""单页即可放下所有数据"""
|
|
params = PaginationParams(page=1, page_size=100)
|
|
meta = PaginationMeta.from_params(params, total=50)
|
|
|
|
assert meta.total_pages == 1
|
|
assert meta.has_next is False
|
|
assert meta.has_prev is False
|
|
|
|
|
|
class TestPaginatedResponse:
|
|
"""PaginatedResponse 测试"""
|
|
|
|
def test_create_success(self):
|
|
"""创建分页响应"""
|
|
params = PaginationParams(page=1, page_size=10)
|
|
data = [1, 2, 3]
|
|
|
|
response = PaginatedResponse.create(data, params, total=25)
|
|
|
|
assert response.data == [1, 2, 3]
|
|
assert response.pagination.page == 1
|
|
assert response.pagination.total == 25
|
|
assert response.pagination.total_pages == 3
|
|
|
|
def test_create_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
|
|
|
|
|
|
class TestPaginateFunction:
|
|
"""paginate 函数测试(内存分页)"""
|
|
|
|
def test_first_page(self):
|
|
"""第一页分页"""
|
|
items = list(range(30))
|
|
params = PaginationParams(page=1, page_size=10)
|
|
|
|
result = paginate(items, params)
|
|
|
|
assert result.data == list(range(10))
|
|
assert result.pagination.total == 30
|
|
assert result.pagination.total_pages == 3
|
|
assert result.pagination.has_next is True
|
|
assert result.pagination.has_prev is False
|
|
|
|
def test_second_page(self):
|
|
"""第二页分页"""
|
|
items = list(range(30))
|
|
params = PaginationParams(page=2, page_size=10)
|
|
|
|
result = paginate(items, params)
|
|
|
|
assert result.data == list(range(10, 20))
|
|
assert result.pagination.page == 2
|
|
|
|
def test_last_page(self):
|
|
"""最后一页分页"""
|
|
items = list(range(25))
|
|
params = PaginationParams(page=3, page_size=10)
|
|
|
|
result = paginate(items, params)
|
|
|
|
assert result.data == list(range(20, 25))
|
|
assert len(result.data) == 5
|
|
assert result.pagination.has_next is False
|
|
|
|
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_page_beyond_total(self):
|
|
"""页码超出总数"""
|
|
items = list(range(5))
|
|
params = PaginationParams(page=10, page_size=10)
|
|
|
|
result = paginate(items, params)
|
|
|
|
assert result.data == []
|
|
assert result.pagination.total == 5
|
|
assert result.pagination.total_pages == 1
|
|
|
|
def test_custom_page_size(self):
|
|
"""自定义每页数量"""
|
|
items = list(range(100))
|
|
params = PaginationParams(page=1, page_size=50)
|
|
|
|
result = paginate(items, params)
|
|
|
|
assert len(result.data) == 50
|
|
assert result.pagination.total_pages == 2
|
|
|
|
def test_single_item(self):
|
|
"""单条数据"""
|
|
items = ["only_one"]
|
|
params = PaginationParams(page=1, page_size=10)
|
|
|
|
result = paginate(items, params)
|
|
|
|
assert result.data == ["only_one"]
|
|
assert result.pagination.total == 1
|
|
assert result.pagination.total_pages == 1
|
|
|
|
def test_generic_type_preserved(self):
|
|
"""泛型类型数据正确"""
|
|
items = [{"id": 1, "name": "a"}, {"id": 2, "name": "b"}]
|
|
params = PaginationParams(page=1, page_size=10)
|
|
|
|
result = paginate(items, params)
|
|
|
|
assert len(result.data) == 2
|
|
assert result.data[0]["id"] == 1
|