"""truncation.py DTO 单元测试。 覆盖 ``TruncationCandidate`` 与 ``TruncationResult`` 的字段赋值、默认值、 不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses import pytest from yuxi.channels.contract.dtos.truncation import ( TruncationCandidate, TruncationResult, ) from yuxi.channels.contract.errors import ValidationError pytestmark = pytest.mark.unit @pytest.mark.unit class TestTruncationCandidate: """截断候选 DTO 测试。""" def test_fields_are_assigned(self): # Arrange # Act candidate = TruncationCandidate(text="hello", source="user_message", length=5) # Assert assert candidate.text == "hello" assert candidate.source == "user_message" assert candidate.length == 5 def test_zero_length_is_valid(self): # Arrange # Act candidate = TruncationCandidate(text="hello", source="user_message", length=0) # Assert - length 为 0 是合法的(非负) assert candidate.length == 0 def test_is_frozen(self): # Arrange candidate = TruncationCandidate(text="hello", source="user_message", length=5) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): candidate.text = "other" # type: ignore[misc] def test_empty_text_raises_validation_error(self): # Arrange # Act / Assert with pytest.raises(ValidationError) as exc_info: TruncationCandidate(text="", source="user_message", length=5) assert exc_info.value.field == "text" def test_empty_source_raises_validation_error(self): # Arrange # Act / Assert with pytest.raises(ValidationError) as exc_info: TruncationCandidate(text="hello", source="", length=5) assert exc_info.value.field == "source" def test_negative_length_raises_validation_error(self): # Arrange # Act / Assert with pytest.raises(ValidationError) as exc_info: TruncationCandidate(text="hello", source="user_message", length=-1) assert exc_info.value.field == "length" @pytest.mark.unit class TestTruncationResult: """截断结果 DTO 测试。""" def test_required_fields_are_assigned(self): # Arrange # Act result = TruncationResult(is_truncated=True) # Assert assert result.is_truncated is True def test_defaults_are_correct(self): # Arrange # Act result = TruncationResult(is_truncated=False) # Assert assert result.missing_content is None assert result.fallback_to_persistent is False assert result.completed_text is None assert result.original_length is None assert result.completed_length is None def test_custom_values_are_assigned(self): # Arrange # Act result = TruncationResult( is_truncated=True, missing_content="missing", fallback_to_persistent=True, completed_text="completed", original_length=100, completed_length=150, ) # Assert assert result.missing_content == "missing" assert result.fallback_to_persistent is True assert result.completed_text == "completed" assert result.original_length == 100 assert result.completed_length == 150 def test_is_frozen(self): # Arrange result = TruncationResult(is_truncated=True) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): result.is_truncated = False # type: ignore[misc]