"""session.py DTO 单元测试。 覆盖 ``ChannelSessionId`` / ``PeerId`` / ``ChatType`` / ``SessionOwner`` / ``OwnerTransferCmd`` / ``TemporarySessionPattern`` / ``CloseSessionCmd`` / ``SessionMessageItem`` / ``SessionStatsResult`` / ``BatchCloseSessionsCmd`` / ``BatchCloseResult`` 的字段赋值、默认值、不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses from datetime import datetime import pytest from yuxi.channels.contract.dtos.common import Operator, OperatorRole from yuxi.channels.contract.dtos.session import ( BatchCloseResult, BatchCloseSessionsCmd, ChannelSessionId, ChatType, CloseSessionCmd, OwnerTransferCmd, PeerId, SessionMessageItem, SessionOwner, SessionStatsResult, TemporarySessionPattern, ) from yuxi.channels.contract.errors import ValidationError pytestmark = pytest.mark.unit def _make_operator() -> Operator: return Operator(user_id="admin-1", role=OperatorRole.ADMIN_USER) @pytest.mark.unit class TestChannelSessionId: """渠道会话 ID DTO 测试。""" def test_value_is_assigned(self): # Act sid = ChannelSessionId(value="session-001") # Assert assert sid.value == "session-001" @pytest.mark.unit class TestPeerId: """对端 ID DTO 测试。""" def test_value_is_assigned(self): # Act pid = PeerId(value="peer-001") # Assert assert pid.value == "peer-001" @pytest.mark.unit class TestChatType: """会话类型枚举测试。""" def test_str_values(self): assert ChatType.P2P == "p2p" assert ChatType.GROUP == "group" assert ChatType.KF == "kf" @pytest.mark.unit class TestSessionOwner: """会话所有者 DTO 测试。""" def test_fields_are_assigned(self): # Act owner = SessionOwner( conversation_id="conv-1", owner_peer_id="peer-1", created_at=datetime(2024, 1, 1), ) # Assert assert owner.owner_peer_id == "peer-1" @pytest.mark.unit class TestOwnerTransferCmd: """所有者转移命令 DTO 测试。""" def test_valid_construction(self): # Act cmd = OwnerTransferCmd( session_id="sess-1", new_owner_id="peer-2", operator=_make_operator(), ) # Assert assert cmd.new_owner_id == "peer-2" def test_empty_session_id_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: OwnerTransferCmd( session_id="", new_owner_id="peer-2", operator=_make_operator(), ) assert exc_info.value.field == "session_id" def test_empty_new_owner_id_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: OwnerTransferCmd( session_id="sess-1", new_owner_id="", operator=_make_operator(), ) assert exc_info.value.field == "new_owner_id" @pytest.mark.unit class TestCloseSessionCmd: """关闭会话命令 DTO 测试。""" def test_valid_construction(self): # Act cmd = CloseSessionCmd( session_id="sess-1", operator=_make_operator(), ) # Assert assert cmd.reason is None def test_empty_session_id_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: CloseSessionCmd(session_id="", operator=_make_operator()) assert exc_info.value.field == "session_id" @pytest.mark.unit class TestBatchCloseSessionsCmd: """批量关闭会话命令 DTO 测试。""" def test_valid_with_session_ids(self): # Act cmd = BatchCloseSessionsCmd( operator=_make_operator(), session_ids=("s1", "s2"), ) # Assert assert cmd.max_count == 100 assert cmd.filter is None def test_valid_with_filter(self): # Act cmd = BatchCloseSessionsCmd( operator=_make_operator(), filter={"channel_type": "wechat"}, ) # Assert assert cmd.session_ids == () def test_both_empty_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: BatchCloseSessionsCmd(operator=_make_operator()) assert exc_info.value.field == "session_ids" def test_max_count_below_min_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: BatchCloseSessionsCmd( operator=_make_operator(), session_ids=("s1",), max_count=0, ) assert exc_info.value.field == "max_count" def test_max_count_above_max_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: BatchCloseSessionsCmd( operator=_make_operator(), session_ids=("s1",), max_count=1001, ) assert exc_info.value.field == "max_count" @pytest.mark.unit class TestSessionStatsResult: """会话统计结果 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act result = SessionStatsResult( message_count=10, user_message_count=5, assistant_message_count=5, started_at=datetime(2024, 1, 1), last_activity_at=datetime(2024, 1, 2), duration_seconds=86400, ) # Assert assert result.first_response_seconds is None assert result.avg_response_seconds is None