"""dashboard.py DTO 单元测试。 覆盖 ``AccountStats`` / ``MessageStats`` / ``SessionStats`` / ``OutboxStatsSummary`` / ``DashboardOverview`` / ``DashboardOverviewQuery`` / ``DashboardDeliveryQuery`` / ``ChannelDeliveryStat`` / ``DashboardDeliveryResult`` / ``DashboardTodoResult`` / ``RealtimeQuery`` / ``ChannelRealtimeStat`` / ``RealtimeMetricsResult`` 的字段赋值、默认值、 不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses from datetime import datetime, timezone import pytest from yuxi.channels.contract.dtos.channel import ChannelType from yuxi.channels.contract.dtos.dashboard import ( AccountStats, ChannelDeliveryStat, ChannelRealtimeStat, DashboardDeliveryQuery, DashboardDeliveryResult, DashboardOverview, DashboardOverviewQuery, DashboardTodoResult, MessageStats, OutboxStatsSummary, RealtimeMetricsResult, RealtimeQuery, SessionStats, ) from yuxi.channels.contract.errors import ValidationError pytestmark = pytest.mark.unit def _aware_dt() -> datetime: return datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) def _naive_dt() -> datetime: return datetime(2024, 1, 1, 12, 0, 0) @pytest.mark.unit class TestAccountStats: """账户统计快照 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act stats = AccountStats( total=10, by_channel={"wechat": 10}, by_status={"active": 8, "disabled": 2} ) # Assert assert stats.by_onboarding_status == {} @pytest.mark.unit class TestDashboardOverviewQuery: """全局总览查询 DTO 测试。""" def test_defaults(self): # Act query = DashboardOverviewQuery() # Assert assert query.channel_type is None assert query.start_time is None def test_naive_start_time_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: DashboardOverviewQuery(start_time=_naive_dt()) assert exc_info.value.field == "start_time" def test_naive_end_time_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: DashboardOverviewQuery(end_time=_naive_dt()) assert exc_info.value.field == "end_time" def test_reversed_time_range_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: DashboardOverviewQuery( start_time=datetime(2024, 1, 2, tzinfo=timezone.utc), end_time=datetime(2024, 1, 1, tzinfo=timezone.utc), ) assert exc_info.value.field == "time_range" @pytest.mark.unit class TestDashboardDeliveryQuery: """投递总览查询 DTO 测试。""" def test_defaults(self): # Act query = DashboardDeliveryQuery() # Assert assert query.channel_type is None def test_naive_start_time_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: DashboardDeliveryQuery(start_time=_naive_dt()) assert exc_info.value.field == "start_time" def test_reversed_time_range_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: DashboardDeliveryQuery( start_time=datetime(2024, 1, 2, tzinfo=timezone.utc), end_time=datetime(2024, 1, 1, tzinfo=timezone.utc), ) assert exc_info.value.field == "time_range" @pytest.mark.unit class TestRealtimeQuery: """实时监控查询 DTO 测试。""" def test_defaults(self): # Act query = RealtimeQuery() # Assert assert query.window_seconds == 60 def test_is_frozen(self): # Arrange query = RealtimeQuery() # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): query.window_seconds = 10 # type: ignore[misc] def test_window_below_min_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: RealtimeQuery(window_seconds=9) assert exc_info.value.field == "window_seconds" def test_window_above_max_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: RealtimeQuery(window_seconds=301) assert exc_info.value.field == "window_seconds" def test_window_at_min_boundary_is_valid(self): # Act query = RealtimeQuery(window_seconds=10) # Assert assert query.window_seconds == 10 def test_window_at_max_boundary_is_valid(self): # Act query = RealtimeQuery(window_seconds=300) # Assert assert query.window_seconds == 300 @pytest.mark.unit class TestDashboardDeliveryResult: """投递总览结果 DTO 测试。""" def test_fields_are_assigned(self): # Act result = DashboardDeliveryResult( total_sent=100, success_count=90, failed_count=10, success_rate=90.0, avg_latency_ms=50.0, p95_latency_ms=100.0, p99_latency_ms=200.0, current_queue_depth=5, dead_letter_count=1, by_channel=( ChannelDeliveryStat(channel_type="wechat", sent=50, success_rate=95.0), ), ) # Assert assert result.success_rate == 90.0 assert result.by_channel[0].channel_type == "wechat" def test_success_rate_can_be_none(self): # Act result = DashboardDeliveryResult( total_sent=0, success_count=0, failed_count=0, success_rate=None, avg_latency_ms=None, p95_latency_ms=None, p99_latency_ms=None, current_queue_depth=0, dead_letter_count=0, by_channel=(), ) # Assert assert result.success_rate is None @pytest.mark.unit class TestDashboardTodoResult: """工作台待办计数聚合结果 DTO 测试。""" def test_fields_are_assigned(self): # Act result = DashboardTodoResult( pending_pairings=3, pending_reviews=5, dead_letter_count=2 ) # Assert assert result.pending_pairings == 3 @pytest.mark.unit class TestRealtimeMetricsResult: """实时监控结果 DTO 测试。""" def test_fields_are_assigned(self): # Act result = RealtimeMetricsResult( window_seconds=60, messages_per_second=10.5, active_sessions=100, active_conversations=95, queue_depth=5, worker_utilization=None, by_channel=( ChannelRealtimeStat(channel_type="wechat", mps=5.0, active_sessions=50), ), ) # Assert assert result.worker_utilization is None assert result.by_channel[0].mps == 5.0