"""outbound.py DTO 单元测试。 覆盖 ``RichMessageButton`` / ``RichMessageOption`` / ``RichMessageSelect`` / ``RichMessageDatePicker`` / ``RichMessageCheckbox`` / ``RichMessageInput`` / ``RichMessage`` / ``RichMessageFields`` / ``OutboundPayload`` / ``FormattedMessage`` / ``TrustedMessage`` / ``FinalMessage`` / ``BatchSendItem`` / ``BatchSendResult`` 的字段赋值、默认值、不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses import pytest from yuxi.channels.contract.dtos.common import Attachment, MessageFormat from yuxi.channels.contract.dtos.outbound import ( BatchSendItem, BatchSendResult, FinalMessage, FormattedMessage, OutboundPayload, RichMessage, RichMessageButton, RichMessageCheckbox, RichMessageDatePicker, RichMessageFields, RichMessageInput, RichMessageOption, RichMessageSelect, TrustedMessage, ) from yuxi.channels.contract.errors import ValidationError pytestmark = pytest.mark.unit @pytest.mark.unit class TestRichMessageButton: """富消息按钮 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act btn = RichMessageButton(label="OK", action="confirm") # Assert assert btn.value is None @pytest.mark.unit class TestRichMessageOption: """富消息选项 DTO 测试。""" def test_fields_are_assigned(self): # Act opt = RichMessageOption(label="Option A", value="a") # Assert assert opt.value == "a" @pytest.mark.unit class TestRichMessageSelect: """富消息下拉选择器组件 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act sel = RichMessageSelect( name="sel1", label="Select", options=(RichMessageOption("A", "a"),) ) # Assert assert sel.placeholder is None assert sel.default_value is None @pytest.mark.unit class TestRichMessage: """富消息 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act msg = RichMessage(text="hello") # Assert assert msg.title is None assert msg.buttons == () assert msg.options == () assert msg.attachments == () assert msg.selects == () def test_is_frozen(self): # Arrange msg = RichMessage(text="hello") # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): msg.text = "other" # type: ignore[misc] @pytest.mark.unit class TestRichMessageFields: """富消息字段 DTO 测试。""" def test_default_fallback_format(self): # Act fields = RichMessageFields(rich_message=RichMessage(text="hi")) # Assert assert fields.fallback_format == MessageFormat.MARKDOWN @pytest.mark.unit class TestOutboundPayload: """出站负载 DTO 测试。""" def test_defaults(self): # Act payload = OutboundPayload() # Assert assert payload.agent_run_id is None assert payload.stream_chunks == () assert payload.rich_message_fields is None assert payload.attachments == () @pytest.mark.unit class TestFormattedMessage: """格式化消息 DTO 测试。""" def test_defaults(self): # Act msg = FormattedMessage(content="hello") # Assert assert msg.format == MessageFormat.TEXT assert msg.rich_message is None assert msg.attachments == () @pytest.mark.unit class TestTrustedMessage: """可信消息 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act msg = TrustedMessage(content="hello", sender_id="sender-1") # Assert assert msg.is_owner is False assert msg.rich_message is None def test_is_frozen(self): # Arrange msg = TrustedMessage(content="hello", sender_id="s1") # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): msg.sender_id = "other" # type: ignore[misc] def test_empty_sender_id_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: TrustedMessage(content="hello", sender_id="") assert exc_info.value.field == "sender_id" def test_empty_content_is_allowed(self): # Act - 流式模式下 trusted-inject 阶段 content 可为空 msg = TrustedMessage(content="", sender_id="s1") # Assert assert msg.content == "" @pytest.mark.unit class TestFinalMessage: """最终消息 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act msg = FinalMessage(content="hello", sender_id="s1") # Assert assert msg.prefix is None assert msg.rich_message is None def test_empty_content_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: FinalMessage(content="", sender_id="s1") assert exc_info.value.field == "content" def test_empty_sender_id_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: FinalMessage(content="hello", sender_id="") assert exc_info.value.field == "sender_id" @pytest.mark.unit class TestBatchSendItem: """批量发送单条结果 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act item = BatchSendItem(peer_id="p1", channel_msg_id="msg-1", success=True) # Assert assert item.error is None def test_is_frozen(self): # Arrange item = BatchSendItem(peer_id="p1", channel_msg_id="m1", success=True) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): item.success = False # type: ignore[misc] @pytest.mark.unit class TestBatchSendResult: """批量发送结果 DTO 测试。""" def test_fields_are_assigned(self): # Arrange items = (BatchSendItem(peer_id="p1", channel_msg_id="m1", success=True),) # Act result = BatchSendResult( total=1, succeeded=("p1",), failed=(), items=items ) # Assert assert result.total == 1 assert result.succeeded == ("p1",)