"""status.py DTO 单元测试。 覆盖 ``EventType`` / ``MessageStatus`` 枚举与 ``StatusPayload`` 的字段 赋值、默认值、不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses from datetime import datetime import pytest from yuxi.channels.contract.dtos.status import ( EventType, MessageStatus, StatusPayload, ) from yuxi.channels.contract.errors import ValidationError pytestmark = pytest.mark.unit @pytest.mark.unit class TestEventType: """事件类型枚举测试。""" def test_enum_values_are_correct(self): assert EventType.MESSAGE == "message" assert EventType.DELIVERED == "delivered" assert EventType.READ == "read" assert EventType.EDITED == "edited" assert EventType.RECALLED == "recalled" assert EventType.UNKNOWN == "unknown" assert EventType.AGENT_MENTION == "agent_mention" def test_enum_inherits_from_str(self): assert issubclass(EventType, str) def test_enum_has_seven_members(self): assert len(list(EventType)) == 7 @pytest.mark.unit class TestMessageStatus: """消息状态枚举测试。""" def test_enum_values_are_correct(self): assert MessageStatus.PENDING == "pending" assert MessageStatus.SENT == "sent" assert MessageStatus.DELIVERED == "delivered" assert MessageStatus.READ == "read" assert MessageStatus.FAILED == "failed" def test_enum_has_five_members(self): assert len(list(MessageStatus)) == 5 @pytest.mark.unit class TestStatusPayload: """状态负载 DTO 测试。""" def test_required_fields_are_assigned(self): # Arrange now = datetime(2026, 1, 1, 12, 0, 0) # Act payload = StatusPayload( event_type=EventType.DELIVERED, ref_channel_msg_id="cm-1", timestamp=now, ) # Assert assert payload.event_type is EventType.DELIVERED assert payload.ref_channel_msg_id == "cm-1" assert payload.timestamp is now assert payload.metadata is None def test_with_metadata(self): # Arrange now = datetime(2026, 1, 1, 12, 0, 0) # Act payload = StatusPayload( event_type=EventType.READ, ref_channel_msg_id="cm-1", timestamp=now, metadata={"read_by": "user-1"}, ) # Assert assert payload.metadata == {"read_by": "user-1"} def test_empty_ref_for_non_message_event_is_valid(self): # Arrange now = datetime(2026, 1, 1, 12, 0, 0) # Act - 非 MESSAGE 事件允许空 ref_channel_msg_id payload = StatusPayload( event_type=EventType.DELIVERED, ref_channel_msg_id="", timestamp=now, ) # Assert assert payload.ref_channel_msg_id == "" def test_is_frozen(self): # Arrange now = datetime(2026, 1, 1, 12, 0, 0) payload = StatusPayload( event_type=EventType.MESSAGE, ref_channel_msg_id="cm-1", timestamp=now, ) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): payload.event_type = EventType.READ # type: ignore[misc] def test_empty_ref_for_message_event_raises_validation_error(self): # Arrange now = datetime(2026, 1, 1, 12, 0, 0) # Act / Assert - MESSAGE 事件必须非空 ref_channel_msg_id with pytest.raises(ValidationError) as exc_info: StatusPayload( event_type=EventType.MESSAGE, ref_channel_msg_id="", timestamp=now, ) assert exc_info.value.field == "ref_channel_msg_id"