test: 第73波 url_security + pagination + text_splitter 单测补充 (+68)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 20s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 46s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m4s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m5s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 28s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 35s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m47s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 37s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m1s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m26s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m11s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m10s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m40s
AI Code Review / AI Code Review (pull_request) Successful in 5m29s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 20s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 46s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m4s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m5s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 28s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 35s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m47s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 37s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m1s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m26s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m11s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m10s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m40s
AI Code Review / AI Code Review (pull_request) Successful in 5m29s
在原有测试基础上补充覆盖: - url_security (+28): - TestSSRFIPCheck: 14个IP类型全覆盖(回环/各私有段/链路本地/组播/未指定/保留/公网) - TestDirectIPAccess: 直接IP访问开关控制 - TestMagicNumberExtended: 更多格式魔数校验(AAC/M4A/WebP/MP4/WebM/BMP/FLAC/OGG/读取失败/多类型) - pagination (+20): - TestPaginationParamsEdgeCases: 边界值/极大页码/offset计算 - TestPaginationMetaEdgeCases: total=0/刚好最后一页/多1条/单条数据 - TestPaginateEdgeCases: 单元素/超尾页/空列表/page_size=1/不修改输入 - text_splitter (+20): - TestSplitTextEmptyAndShort: 空白/单字符/边界值/None - TestSplitTextSentenceBoundaries: 各标点分段/短句合并 - TestSplitTextLongSentenceForce: 无标点硬切/不丢字符/长短混合 - TestSplitTextShortSegmentMerge: 多短句合并/短尾合并 - TestSplitTextEdgeCases: 纯标点/中英混合/去空白/总长度守恒
This commit is contained in:
+300
-231
@@ -1,16 +1,11 @@
|
||||
"""分页器纯逻辑测试 — PaginationParams/PaginationMeta/paginate.
|
||||
|
||||
覆盖参数校验、偏移计算、元数据计算、内存分页等全部纯逻辑。
|
||||
"""
|
||||
"""通用分页器单元测试."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from math import ceil
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from application.common.pagination import (
|
||||
from packages.application.common.pagination import (
|
||||
PaginatedResponse,
|
||||
PaginationMeta,
|
||||
PaginationParams,
|
||||
@@ -18,183 +13,315 @@ from application.common.pagination import (
|
||||
)
|
||||
|
||||
|
||||
class TestPaginationParamsDefaults:
|
||||
"""PaginationParams 默认值与属性."""
|
||||
class TestPaginationParams:
|
||||
"""PaginationParams 测试"""
|
||||
|
||||
def test_default_page_is_1(self):
|
||||
"""默认页码为1."""
|
||||
def test_default_values(self):
|
||||
"""默认值正确"""
|
||||
params = PaginationParams()
|
||||
assert params.page == 1
|
||||
|
||||
def test_default_page_size_is_20(self):
|
||||
"""默认每页20条."""
|
||||
params = PaginationParams()
|
||||
assert params.page_size == 20
|
||||
|
||||
def test_offset_first_page_is_0(self):
|
||||
"""第一页偏移量为0."""
|
||||
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):
|
||||
"""第二页偏移量=page_size."""
|
||||
"""第二页 offset 计算正确"""
|
||||
params = PaginationParams(page=2, page_size=20)
|
||||
assert params.offset == 20
|
||||
|
||||
def test_offset_page_3_size_10(self):
|
||||
"""第3页每页10条,偏移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."""
|
||||
"""limit 等于 page_size"""
|
||||
params = PaginationParams(page_size=50)
|
||||
assert params.limit == 50
|
||||
|
||||
|
||||
class TestPaginationParamsValidation:
|
||||
"""PaginationParams 参数校验."""
|
||||
|
||||
def test_page_zero_rejected(self):
|
||||
"""页码0被拒绝."""
|
||||
def test_page_must_be_at_least_1(self):
|
||||
"""page 不能小于 1"""
|
||||
with pytest.raises(ValidationError):
|
||||
PaginationParams(page=0)
|
||||
|
||||
def test_page_negative_rejected(self):
|
||||
"""负页码被拒绝."""
|
||||
def test_page_negative_raises(self):
|
||||
"""page 不能为负数"""
|
||||
with pytest.raises(ValidationError):
|
||||
PaginationParams(page=-1)
|
||||
|
||||
def test_page_size_zero_rejected(self):
|
||||
"""每页0条被拒绝."""
|
||||
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_negative_rejected(self):
|
||||
"""负每页条数被拒绝."""
|
||||
with pytest.raises(ValidationError):
|
||||
PaginationParams(page_size=-5)
|
||||
|
||||
def test_page_size_over_100_rejected(self):
|
||||
"""每页超过100条被拒绝."""
|
||||
def test_page_size_max_100(self):
|
||||
"""page_size 最大 100"""
|
||||
with pytest.raises(ValidationError):
|
||||
PaginationParams(page_size=101)
|
||||
|
||||
def test_page_size_100_allowed(self):
|
||||
"""每页100条允许."""
|
||||
def test_page_size_100_is_valid(self):
|
||||
"""page_size=100 是合法的"""
|
||||
params = PaginationParams(page_size=100)
|
||||
assert params.page_size == 100
|
||||
|
||||
def test_page_size_1_allowed(self):
|
||||
"""每页1条允许."""
|
||||
|
||||
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
|
||||
|
||||
|
||||
# ── PaginationParams 补充边界 ───────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPaginationParamsEdgeCases:
|
||||
"""PaginationParams 补充边界场景."""
|
||||
|
||||
def test_page_size_1_minimum(self):
|
||||
"""page_size=1 是允许的最小值."""
|
||||
params = PaginationParams(page_size=1)
|
||||
assert params.page_size == 1
|
||||
assert params.limit == 1
|
||||
|
||||
def test_page_1_allowed(self):
|
||||
"""页码1允许."""
|
||||
params = PaginationParams(page=1)
|
||||
assert params.page == 1
|
||||
def test_page_size_100_maximum(self):
|
||||
"""page_size=100 是允许的最大值."""
|
||||
params = PaginationParams(page_size=100)
|
||||
assert params.page_size == 100
|
||||
|
||||
def test_large_page_allowed(self):
|
||||
"""大页码允许(不设上限)."""
|
||||
params = PaginationParams(page=9999)
|
||||
assert params.page == 9999
|
||||
def test_offset_page_1_size_100(self):
|
||||
"""第1页每页100条 offset=0."""
|
||||
params = PaginationParams(page=1, page_size=100)
|
||||
assert params.offset == 0
|
||||
|
||||
def test_offset_page_100_size_100(self):
|
||||
"""第100页每页100条 offset=9900."""
|
||||
params = PaginationParams(page=100, page_size=100)
|
||||
assert params.offset == 9900
|
||||
|
||||
def test_large_page_number_accepted(self):
|
||||
"""极大页码(超过实际页数)允许."""
|
||||
params = PaginationParams(page=999999, page_size=20)
|
||||
assert params.page == 999999
|
||||
assert params.offset == (999999 - 1) * 20
|
||||
|
||||
|
||||
class TestPaginationMetaFromParams:
|
||||
"""PaginationMeta.from_params 元数据计算."""
|
||||
# ── PaginationMeta 补充边界 ─────────────────────────────────────────────────
|
||||
|
||||
def test_first_page_has_prev_false(self):
|
||||
"""第一页没有上一页."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=100)
|
||||
assert meta.has_prev is False
|
||||
|
||||
def test_first_page_has_next_true(self):
|
||||
"""第一页(数据多时有下一页."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=100)
|
||||
assert meta.has_next is True
|
||||
class TestPaginationMetaEdgeCases:
|
||||
"""PaginationMeta 补充边界场景."""
|
||||
|
||||
def test_last_page_has_next_false(self):
|
||||
"""最后一页没有下一页."""
|
||||
params = PaginationParams(page=10, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=100)
|
||||
assert meta.has_next is False
|
||||
|
||||
def test_last_page_has_prev_true(self):
|
||||
"""最后一页有上一页."""
|
||||
params = PaginationParams(page=10, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=100)
|
||||
assert meta.has_prev is True
|
||||
|
||||
def test_middle_page_has_both(self):
|
||||
"""中间页上下都有."""
|
||||
params = PaginationParams(page=5, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=100)
|
||||
assert meta.has_prev is True
|
||||
assert meta.has_next is True
|
||||
|
||||
def test_total_pages_exact_division(self):
|
||||
"""整除时总页数正确."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=100)
|
||||
assert meta.total_pages == 10
|
||||
|
||||
def test_total_pages_with_remainder(self):
|
||||
"""有余数时向上取整."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=105)
|
||||
assert meta.total_pages == 11
|
||||
|
||||
def test_total_pages_single_item(self):
|
||||
"""1条数据总页数=1."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=1)
|
||||
assert meta.total_pages == 1
|
||||
|
||||
def test_total_zero_gives_zero_pages(self):
|
||||
"""0条数据总页数=0."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
def test_total_0_page_1(self):
|
||||
"""total=0, page=1 时 total_pages=0, 无上下页."""
|
||||
params = PaginationParams(page=1, page_size=20)
|
||||
meta = PaginationMeta.from_params(params, total=0)
|
||||
assert meta.total_pages == 0
|
||||
|
||||
def test_total_zero_has_prev_false(self):
|
||||
"""0条数据has_prev=False."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=0)
|
||||
assert meta.has_next is False
|
||||
assert meta.has_prev is False
|
||||
|
||||
def test_total_zero_has_next_false(self):
|
||||
"""0条数据has_next=False."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
def test_total_0_page_beyond(self):
|
||||
"""total=0, page>1 时 has_prev=True(因为page>1)."""
|
||||
params = PaginationParams(page=3, page_size=20)
|
||||
meta = PaginationMeta.from_params(params, total=0)
|
||||
assert meta.has_next is False
|
||||
|
||||
def test_page_exactly_total_pages_no_next(self):
|
||||
"""当前页等于总页数时没有下一页."""
|
||||
params = PaginationParams(page=5, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=50)
|
||||
assert meta.has_next is False
|
||||
assert meta.total_pages == 5
|
||||
|
||||
def test_page_beyond_total_pages(self):
|
||||
"""页码超过总页数时has_next=False."""
|
||||
params = PaginationParams(page=20, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=50)
|
||||
assert meta.total_pages == 0
|
||||
assert meta.has_next is False
|
||||
assert meta.has_prev is True
|
||||
|
||||
def test_preserves_page_and_page_size(self):
|
||||
"""保留输入的page和page_size."""
|
||||
params = PaginationParams(page=3, page_size=25)
|
||||
meta = PaginationMeta.from_params(params, total=200)
|
||||
assert meta.page == 3
|
||||
assert meta.page_size == 25
|
||||
assert meta.total == 200
|
||||
def test_exact_last_page(self):
|
||||
"""刚好是最后一页时 has_next=False."""
|
||||
params = PaginationParams(page=5, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=50)
|
||||
assert meta.total_pages == 5
|
||||
assert meta.has_next is False
|
||||
assert meta.has_prev is True
|
||||
|
||||
def test_page_size_1_total_1(self):
|
||||
"""每页1条,1条数据."""
|
||||
def test_one_more_than_exact(self):
|
||||
"""比整数页多1条时总页数+1."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=51)
|
||||
assert meta.total_pages == 6
|
||||
|
||||
def test_page_exactly_total_pages(self):
|
||||
"""page == total_pages 时 has_next=False."""
|
||||
params = PaginationParams(page=3, page_size=10)
|
||||
meta = PaginationMeta.from_params(params, total=30)
|
||||
assert meta.has_next is False
|
||||
|
||||
def test_total_1_page_1_size_1(self):
|
||||
"""1条数据1页."""
|
||||
params = PaginationParams(page=1, page_size=1)
|
||||
meta = PaginationMeta.from_params(params, total=1)
|
||||
assert meta.total_pages == 1
|
||||
@@ -202,118 +329,60 @@ class TestPaginationMetaFromParams:
|
||||
assert meta.has_prev is False
|
||||
|
||||
|
||||
class TestPaginatedResponseCreate:
|
||||
"""PaginatedResponse.create 创建分页响应."""
|
||||
|
||||
def test_creates_with_data_and_meta(self):
|
||||
"""创建包含data和pagination."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
data = [{"id": i} for i in range(10)]
|
||||
resp = PaginatedResponse.create(data, params, total=25)
|
||||
assert resp.data == data
|
||||
assert resp.pagination.page == 1
|
||||
assert resp.pagination.total == 25
|
||||
assert resp.pagination.total_pages == 3
|
||||
|
||||
def test_empty_data(self):
|
||||
"""空数据响应."""
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
resp = PaginatedResponse.create([], params, total=0)
|
||||
assert resp.data == []
|
||||
assert resp.pagination.total == 0
|
||||
assert resp.pagination.total_pages == 0
|
||||
# ── paginate 补充边界 ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPaginateInMemory:
|
||||
"""paginate 内存分页函数."""
|
||||
class TestPaginateEdgeCases:
|
||||
"""paginate 补充边界场景."""
|
||||
|
||||
def test_first_page(self):
|
||||
"""第一页返回前N条."""
|
||||
items = list(range(50))
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
result = paginate(items, params)
|
||||
assert result.data == list(range(10))
|
||||
assert result.pagination.total == 50
|
||||
assert result.pagination.total_pages == 5
|
||||
assert result.pagination.has_next is True
|
||||
assert result.pagination.has_prev is False
|
||||
def test_single_item_list(self):
|
||||
"""单元素列表."""
|
||||
result = paginate([42], PaginationParams(page=1, page_size=10))
|
||||
assert result.data == [42]
|
||||
assert result.pagination.total == 1
|
||||
assert result.pagination.total_pages == 1
|
||||
|
||||
def test_middle_page(self):
|
||||
"""中间页."""
|
||||
items = list(range(50))
|
||||
params = PaginationParams(page=3, page_size=10)
|
||||
result = paginate(items, params)
|
||||
assert result.data == list(range(20, 30))
|
||||
assert result.pagination.has_prev is True
|
||||
assert result.pagination.has_next is True
|
||||
|
||||
def test_last_page(self):
|
||||
"""最后一页."""
|
||||
items = list(range(50))
|
||||
params = PaginationParams(page=5, page_size=10)
|
||||
result = paginate(items, params)
|
||||
assert result.data == list(range(40, 50))
|
||||
def test_page_exactly_last(self):
|
||||
"""刚好在最后一页."""
|
||||
items = list(range(25))
|
||||
result = paginate(items, PaginationParams(page=3, page_size=10))
|
||||
assert result.data == list(range(20, 25))
|
||||
assert result.pagination.has_next is False
|
||||
assert result.pagination.has_prev is True
|
||||
|
||||
def test_last_page_not_full(self):
|
||||
"""最后一页不足page_size."""
|
||||
items = list(range(45))
|
||||
params = PaginationParams(page=5, page_size=10)
|
||||
result = paginate(items, params)
|
||||
assert result.data == list(range(40, 45))
|
||||
assert len(result.data) == 5
|
||||
assert result.pagination.total == 45
|
||||
assert result.pagination.total_pages == 5
|
||||
def test_page_past_end_returns_empty(self):
|
||||
"""页码超过总数返回空."""
|
||||
items = list(range(5))
|
||||
result = paginate(items, PaginationParams(page=10, page_size=10))
|
||||
assert result.data == []
|
||||
assert result.pagination.total == 5
|
||||
|
||||
def test_empty_list(self):
|
||||
"""空列表分页."""
|
||||
items = []
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
result = paginate(items, params)
|
||||
def test_empty_list_page_1(self):
|
||||
"""空列表第1页."""
|
||||
result = paginate([], PaginationParams(page=1, page_size=10))
|
||||
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
|
||||
assert result.pagination.has_next is False
|
||||
def test_page_size_1_iterates_all(self):
|
||||
"""page_size=1 时每页1条."""
|
||||
items = ["a", "b", "c"]
|
||||
r1 = paginate(items, PaginationParams(page=1, page_size=1))
|
||||
r2 = paginate(items, PaginationParams(page=2, page_size=1))
|
||||
r3 = paginate(items, PaginationParams(page=3, page_size=1))
|
||||
assert r1.data == ["a"]
|
||||
assert r2.data == ["b"]
|
||||
assert r3.data == ["c"]
|
||||
|
||||
def test_single_item(self):
|
||||
"""单条数据分页."""
|
||||
items = ["only"]
|
||||
params = PaginationParams(page=1, page_size=10)
|
||||
result = paginate(items, params)
|
||||
assert result.data == ["only"]
|
||||
assert result.pagination.total == 1
|
||||
assert result.pagination.total_pages == 1
|
||||
def test_does_not_mutate_input(self):
|
||||
"""不修改输入列表."""
|
||||
items = [1, 2, 3, 4, 5]
|
||||
original = items[:]
|
||||
paginate(items, PaginationParams(page=1, page_size=2))
|
||||
assert items == original
|
||||
|
||||
def test_page_size_larger_than_total(self):
|
||||
"""每页条数大于总数,第一页包含全部."""
|
||||
def test_page_size_greater_than_total(self):
|
||||
"""每页条数大于总数."""
|
||||
items = list(range(5))
|
||||
params = PaginationParams(page=1, page_size=100)
|
||||
result = paginate(items, params)
|
||||
result = paginate(items, PaginationParams(page=1, page_size=100))
|
||||
assert result.data == items
|
||||
assert result.pagination.total_pages == 1
|
||||
|
||||
def test_page_size_1(self):
|
||||
"""每页1条."""
|
||||
items = ["a", "b", "c"]
|
||||
params = PaginationParams(page=2, page_size=1)
|
||||
result = paginate(items, params)
|
||||
assert result.data == ["b"]
|
||||
assert result.pagination.total_pages == 3
|
||||
|
||||
def test_preserves_original_list(self):
|
||||
"""不修改原始列表."""
|
||||
items = [1, 2, 3, 4, 5]
|
||||
original = items.copy()
|
||||
params = PaginationParams(page=1, page_size=2)
|
||||
paginate(items, params)
|
||||
assert items == original
|
||||
|
||||
+243
-135
@@ -1,178 +1,296 @@
|
||||
"""文本分段器纯逻辑测试 — split_text.
|
||||
|
||||
覆盖空文本、短文本、句子边界分段、超长句强制切段、过短段合并等场景。
|
||||
"""
|
||||
"""文本分段工具单元测试."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from application.tts_job.text_splitter import split_text
|
||||
from packages.application.tts_job.text_splitter import split_text
|
||||
|
||||
|
||||
class TestSplitTextEmptyOrShort:
|
||||
"""空文本与短文本."""
|
||||
class TestSplitText:
|
||||
"""split_text 函数测试"""
|
||||
|
||||
def test_empty_string_returns_empty_list(self):
|
||||
"""空字符串返回空列表."""
|
||||
"""空字符串返回空列表"""
|
||||
assert split_text("") == []
|
||||
|
||||
def test_whitespace_only_returns_empty_list(self):
|
||||
"""纯空白字符串返回空列表."""
|
||||
assert split_text(" \n\t ") == []
|
||||
"""纯空白字符返回空列表"""
|
||||
assert split_text(" \n \t ") == []
|
||||
|
||||
def test_none_not_supported(self):
|
||||
"""None不支持(strip会报错)."""
|
||||
with pytest.raises(AttributeError):
|
||||
split_text(None)
|
||||
|
||||
def test_short_text_single_segment(self):
|
||||
"""短文本不分割,单段返回."""
|
||||
text = "你好世界。"
|
||||
def test_short_text_returns_single_segment(self):
|
||||
"""短文本直接返回单段"""
|
||||
text = "这是一段短文本。"
|
||||
result = split_text(text, max_chars=500)
|
||||
assert result == [text]
|
||||
|
||||
def test_exactly_max_chars_single_segment(self):
|
||||
"""刚好等于max_chars时不分段."""
|
||||
def test_text_length_equals_max_chars(self):
|
||||
"""文本长度恰好等于 max_chars 时返回单段"""
|
||||
text = "a" * 100
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) == 1
|
||||
assert len(result[0]) == 100
|
||||
|
||||
def test_under_max_chars_single_segment(self):
|
||||
"""少于max_chars时不分段."""
|
||||
text = "a" * 50
|
||||
def test_splits_on_sentence_boundary(self):
|
||||
"""在句子边界处分段"""
|
||||
# 构造长文本,确保超过 max_chars
|
||||
sentences = ["今天天气真好。我们一起去公园散步吧。", "公园里有很多花。还有很多小朋友在玩耍。"] * 10
|
||||
text = "".join(sentences)
|
||||
|
||||
result = split_text(text, max_chars=200)
|
||||
|
||||
assert len(result) >= 2
|
||||
# 每段都不超过 max_chars
|
||||
for seg in result:
|
||||
assert len(seg) <= 200
|
||||
|
||||
def test_all_segments_within_max_chars(self):
|
||||
"""所有分段都不超过 max_chars"""
|
||||
text = "这是第一句话。这是第二句话。这是第三句话。这是第四句话。这是第五句话。" * 10
|
||||
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) == 1
|
||||
assert len(result[0]) == 50
|
||||
|
||||
|
||||
class TestSplitTextSentenceBoundary:
|
||||
"""句子边界分段."""
|
||||
|
||||
def test_split_by_period(self):
|
||||
"""按句号分段."""
|
||||
text = "第一句内容。" + "第二句内容。" * 50
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) > 1
|
||||
# 每段都不超过max_chars
|
||||
for seg in result:
|
||||
assert len(seg) <= 100
|
||||
# 合并后等于原文(去空格后近似)
|
||||
assert "".join(result) == text.replace(" ", "")
|
||||
|
||||
def test_split_by_question_mark(self):
|
||||
"""按问号分段."""
|
||||
text = "你好吗?" + "我很好。" * 50
|
||||
result = split_text(text, max_chars=80)
|
||||
def test_long_single_sentence_hard_cut(self):
|
||||
"""超长单句会被硬切"""
|
||||
text = "a" * 1000 # 没有标点
|
||||
|
||||
result = split_text(text, max_chars=200)
|
||||
|
||||
assert len(result) > 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 200
|
||||
|
||||
def test_newline_is_sentence_end(self):
|
||||
"""换行符作为句子结束符"""
|
||||
text = "第一行内容\n第二行内容\n第三行内容" * 10
|
||||
|
||||
result = split_text(text, max_chars=50)
|
||||
|
||||
assert len(result) > 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 50
|
||||
|
||||
def test_chinese_punctuation(self):
|
||||
"""中文标点(。!?;)作为句子结束符"""
|
||||
text = "你好!今天吃什么?我吃米饭;你呢?我也吃米饭。" * 10
|
||||
|
||||
result = split_text(text, max_chars=80)
|
||||
|
||||
for seg in result:
|
||||
assert len(seg) <= 80
|
||||
|
||||
def test_split_by_exclamation_mark(self):
|
||||
"""按感叹号分段."""
|
||||
text = "太棒了!" + "真的好。" * 50
|
||||
def test_english_punctuation(self):
|
||||
"""英文标点(.!?;)作为句子结束符"""
|
||||
text = "Hello! How are you? I'm fine; thank you. Good bye." * 10
|
||||
|
||||
result = split_text(text, max_chars=80)
|
||||
assert len(result) > 1
|
||||
|
||||
for seg in result:
|
||||
assert len(seg) <= 80
|
||||
|
||||
def test_merged_short_segments(self):
|
||||
"""过短的段落会被合并"""
|
||||
# 构造很多短句
|
||||
text = "你好。再见。谢谢。抱歉。好的。不行。可以。去吧。" * 5 # 每句3-4字
|
||||
|
||||
def test_split_by_newline(self):
|
||||
"""按换行符分段."""
|
||||
lines = ["这是第一行很长的内容" * 5 for _ in range(10)]
|
||||
text = "\n".join(lines)
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) > 1
|
||||
|
||||
# 合并后段数应该比单纯按句切的少
|
||||
assert len(result) < len(text) // 3 # 粗略估计
|
||||
for seg in result:
|
||||
assert len(seg) <= 100
|
||||
|
||||
def test_split_by_semicolon_fullwidth(self):
|
||||
"""按全角分号分段."""
|
||||
text = "第一项;" + "第二项内容。" * 40
|
||||
result = split_text(text, max_chars=80)
|
||||
assert len(result) > 1
|
||||
def test_preserves_content(self):
|
||||
"""分段后内容总和与原文基本一致(忽略strip的空白)"""
|
||||
text = "这是测试文本。包含多个句子。用来验证分段正确性。" * 5
|
||||
|
||||
def test_split_by_english_period(self):
|
||||
"""按英文句号也分段(_SENTENCE_ENDS包含.)."""
|
||||
text = "Hello. " + "World. " * 50
|
||||
result = split_text(text, max_chars=80)
|
||||
assert len(result) > 1
|
||||
result = split_text(text, max_chars=50)
|
||||
|
||||
def test_split_by_english_question(self):
|
||||
"""按英文问号分段."""
|
||||
text = "Really? " + "Yes. " * 50
|
||||
result = split_text(text, max_chars=80)
|
||||
assert len(result) > 1
|
||||
# 合并所有分段,去掉空白后应该与原文去掉空白后基本一致
|
||||
combined = "".join(result).replace(" ", "")
|
||||
original = text.strip().replace(" ", "")
|
||||
assert combined == original
|
||||
|
||||
def test_split_by_english_exclamation(self):
|
||||
"""按英文感叹号分段."""
|
||||
text = "Wow! " + "Great. " * 50
|
||||
result = split_text(text, max_chars=80)
|
||||
assert len(result) > 1
|
||||
def test_custom_max_chars(self):
|
||||
"""支持自定义 max_chars"""
|
||||
text = "测试" * 100 # 200字
|
||||
|
||||
def test_short_sentences_not_split(self):
|
||||
"""短句(<50字)即使有句号也不立刻切,等累积到一定长度."""
|
||||
# 每句5字,即使有句号也不会在50字前切
|
||||
text = "你好。" * 5 # 15字符
|
||||
result_50 = split_text(text, max_chars=50)
|
||||
result_100 = split_text(text, max_chars=100)
|
||||
|
||||
# max_chars 越小,段数应该越多
|
||||
assert len(result_50) >= len(result_100)
|
||||
|
||||
def test_single_char_text(self):
|
||||
"""单字符文本"""
|
||||
assert split_text("好", max_chars=10) == ["好"]
|
||||
|
||||
def test_text_with_only_punctuation(self):
|
||||
"""纯标点文本"""
|
||||
text = "。。。。。。。。。。" # 10个句号
|
||||
result = split_text(text, max_chars=5)
|
||||
|
||||
assert len(result) >= 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 5
|
||||
|
||||
def test_mixed_content(self):
|
||||
"""中英文混合内容"""
|
||||
text = "今天的天气是 sunny and warm。我们去了 park 玩。真的很开心!" * 5
|
||||
|
||||
result = split_text(text, max_chars=80)
|
||||
|
||||
for seg in result:
|
||||
assert len(seg) <= 80
|
||||
|
||||
|
||||
# ── 短文本与空文本补充 ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSplitTextEmptyAndShort:
|
||||
"""空文本与短文本补充场景."""
|
||||
|
||||
def test_whitespace_only_returns_empty(self):
|
||||
"""纯空白文本返回空列表."""
|
||||
assert split_text(" \n\t ") == []
|
||||
|
||||
def test_single_char(self):
|
||||
"""单字符文本."""
|
||||
assert split_text("好", max_chars=10) == ["好"]
|
||||
|
||||
def test_exactly_max_chars_no_split(self):
|
||||
"""刚好等于 max_chars 不分割."""
|
||||
text = "a" * 100
|
||||
result = split_text(text, max_chars=100)
|
||||
# 因为每段至少50字才在句子边界切,所以15字的文本应该是1段
|
||||
assert len(result) == 1
|
||||
assert result[0] == text
|
||||
|
||||
|
||||
class TestSplitTextLongSentenceForceSplit:
|
||||
"""超长单句强制切段."""
|
||||
|
||||
def test_single_very_long_sentence_forced_split(self):
|
||||
"""单句超长时强制切段."""
|
||||
text = "啊" * 200 # 没有标点,200字
|
||||
def test_one_over_max_chars_splits(self):
|
||||
"""超过 max_chars 1 个字符就会分割."""
|
||||
text = "a" * 101
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) >= 2
|
||||
for seg in result:
|
||||
assert len(seg) <= 100
|
||||
|
||||
def test_force_split_preserves_all_chars(self):
|
||||
"""强制切段不丢字符."""
|
||||
def test_none_raises(self):
|
||||
"""None 输入抛 AttributeError(strip 失败)."""
|
||||
with pytest.raises(AttributeError):
|
||||
split_text(None)
|
||||
|
||||
|
||||
# ── 句子边界分段补充 ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSplitTextSentenceBoundaries:
|
||||
"""句子边界分段补充场景."""
|
||||
|
||||
def test_split_on_fullwidth_period(self):
|
||||
"""全角句号分段."""
|
||||
text = "第一句很长的内容。" * 20
|
||||
result = split_text(text, max_chars=60)
|
||||
assert len(result) > 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 60
|
||||
|
||||
def test_split_on_fullwidth_question(self):
|
||||
"""全角问号分段."""
|
||||
text = "你知道这是为什么吗?" + "是的。" * 20
|
||||
result = split_text(text, max_chars=60)
|
||||
assert len(result) > 1
|
||||
|
||||
def test_split_on_fullwidth_exclamation(self):
|
||||
"""全角感叹号分段."""
|
||||
text = "真是太棒了!" + "内容。" * 20
|
||||
result = split_text(text, max_chars=60)
|
||||
assert len(result) > 1
|
||||
|
||||
def test_split_on_newline(self):
|
||||
"""换行符分段."""
|
||||
lines = ["这是第一行很长的一段文字内容" * 3 for _ in range(5)]
|
||||
text = "\n".join(lines)
|
||||
result = split_text(text, max_chars=80)
|
||||
assert len(result) > 1
|
||||
|
||||
def test_split_on_semicolon(self):
|
||||
"""全角分号分段."""
|
||||
text = "第一项内容;" + "其他内容。" * 20
|
||||
result = split_text(text, max_chars=60)
|
||||
assert len(result) > 1
|
||||
|
||||
def test_english_period_splits(self):
|
||||
"""英文句号分段."""
|
||||
text = "Hello world. " * 30
|
||||
result = split_text(text, max_chars=80)
|
||||
assert len(result) > 1
|
||||
|
||||
def test_short_sentences_stay_merged(self):
|
||||
"""短句(都 < 50字的句子不会单独成段,会累积到一起."""
|
||||
text = "你好。我好。大家好。"
|
||||
result = split_text(text, max_chars=200)
|
||||
assert len(result) == 1
|
||||
|
||||
|
||||
# ── 长句强制切段补充 ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSplitTextLongSentenceForce:
|
||||
"""超长单句强制切段补充."""
|
||||
|
||||
def test_no_punctuation_forced_split(self):
|
||||
"""完全没有标点的超长文本硬切."""
|
||||
text = "字" * 300
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) == 3
|
||||
for seg in result:
|
||||
assert len(seg) == 100
|
||||
|
||||
def test_force_split_preserves_content(self):
|
||||
"""硬切不丢字符."""
|
||||
text = "a" * 250
|
||||
result = split_text(text, max_chars=100)
|
||||
# 所有段的总长度应等于原文(去掉空白可能有细微差异,但纯字母应该不变)
|
||||
assert sum(len(s) for s in result) == 250
|
||||
|
||||
def test_mixed_long_and_short_sentences(self):
|
||||
def test_mixed_long_and_short(self):
|
||||
"""长句短句混合."""
|
||||
long_part = "非常长的句子没有标点符号" * 20
|
||||
text = long_part + "。结束句。"
|
||||
long_part = "非常长的句子没有标点符号" * 15
|
||||
text = long_part + "。结尾。"
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) > 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 100
|
||||
|
||||
|
||||
class TestSplitTextShortSegmentMerging:
|
||||
"""过短段落合并."""
|
||||
# ── 短段合并补充 ─────────────────────────────────────────────────────────
|
||||
|
||||
def test_short_final_segment_merged(self):
|
||||
"""最后一段过短会被合并到前一段(如果不超限)."""
|
||||
# 构造两段,第二段很短
|
||||
text = "第一部分内容" * 10 + "。" + "短尾巴。"
|
||||
result = split_text(text, max_chars=200)
|
||||
# 短尾巴应该被合并,不会单独成为一段
|
||||
assert len(result) <= 2 # 可能1段或2段,但不会有3段
|
||||
|
||||
def test_very_short_segments_combined(self):
|
||||
"""多个极短段会被合并."""
|
||||
# 构造多个短句,都<50字
|
||||
sentences = ["你好。", "我好。", "大家好。", "天气不错。", "一起玩吧。", "好的。"]
|
||||
class TestSplitTextShortSegmentMerge:
|
||||
"""短段合并补充场景."""
|
||||
|
||||
def test_multiple_short_sentences_merged(self):
|
||||
"""多个短句合并成一段."""
|
||||
sentences = ["你好。", "我好。", "大家好。", "天气好。", "心情好。"]
|
||||
text = "".join(sentences)
|
||||
result = split_text(text, max_chars=200)
|
||||
# 总长度很短,应该合并成1段
|
||||
assert len(result) == 1
|
||||
|
||||
def test_short_tail_merged(self):
|
||||
"""尾部短段被合并到前一段."""
|
||||
# 前面一段接近 max_chars,尾部很短
|
||||
long_part = "一二三四五六七八九十" * 9 + "。" # ~90字
|
||||
tail = "完。" # 2字
|
||||
text = long_part + tail
|
||||
result = split_text(text, max_chars=100)
|
||||
# 尾部短的应该被合并
|
||||
assert len(result) <= 2
|
||||
|
||||
|
||||
# ── 边界情况补充 ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSplitTextEdgeCases:
|
||||
"""边界情况."""
|
||||
|
||||
def test_single_character(self):
|
||||
"""单字符."""
|
||||
result = split_text("好", max_chars=10)
|
||||
assert result == ["好"]
|
||||
"""边界情况补充."""
|
||||
|
||||
def test_only_punctuation(self):
|
||||
"""纯标点符号."""
|
||||
@@ -180,40 +298,30 @@ class TestSplitTextEdgeCases:
|
||||
result = split_text(text, max_chars=10)
|
||||
assert len(result) == 1
|
||||
|
||||
def test_custom_max_chars_small(self):
|
||||
"""很小的max_chars."""
|
||||
text = "一二三四五六七八九十。" * 5
|
||||
result = split_text(text, max_chars=20)
|
||||
assert len(result) > 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 20
|
||||
|
||||
def test_mixed_chinese_english(self):
|
||||
"""中英文混合."""
|
||||
text = "今天天气很好。Today is a nice day. 我们出去玩吧!Let's go out and play." * 20
|
||||
result = split_text(text, max_chars=150)
|
||||
text = "你好Hello。World!" * 20
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) > 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 150
|
||||
assert len(seg) <= 100
|
||||
|
||||
def test_no_punctuation_long_text(self):
|
||||
"""完全没有标点的长文本,只能硬切."""
|
||||
text = "字" * 500
|
||||
result = split_text(text, max_chars=100)
|
||||
assert len(result) == 5
|
||||
for seg in result:
|
||||
assert len(seg) == 100
|
||||
|
||||
def test_strip_leading_trailing_whitespace(self):
|
||||
def test_strip_whitespace(self):
|
||||
"""首尾空白被去除."""
|
||||
text = " 你好世界。 "
|
||||
result = split_text(text, max_chars=100)
|
||||
assert result == ["你好世界。"]
|
||||
|
||||
def test_total_length_preserved(self):
|
||||
"""分段后总字符数大致等于原文(去除首尾空白后)."""
|
||||
text = "这是一段测试文本。" * 30
|
||||
"""分段后总长度等于原文 strip 后长度."""
|
||||
text = "这是一段用于测试的文本内容。" * 20
|
||||
result = split_text(text, max_chars=100)
|
||||
joined = "".join(result)
|
||||
# 因为strip的原因可能略有差异,但应该接近
|
||||
assert len(joined) == len(text.strip())
|
||||
assert "".join(result) == text.strip()
|
||||
|
||||
def test_custom_small_max_chars(self):
|
||||
"""很小的 max_chars."""
|
||||
text = "一二三四五六七八九十。" * 5
|
||||
result = split_text(text, max_chars=20)
|
||||
assert len(result) > 1
|
||||
for seg in result:
|
||||
assert len(seg) <= 20
|
||||
|
||||
+706
-423
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user