1. 清理测试文件中未使用的导入与冗余代码 2. 修复审计日志与批量操作的空值约束,统一填充"global"作为默认渠道 3. 调整批量消息撤回的响应语义,对齐其他端点的部分成功契约 4. 修复访问规则批量克隆的唯一约束问题,新增后缀自动处理逻辑 5. 替换anyio为asyncio并行调用,修正时间UTC导入路径 6. 优化前端外部系统概览页的刷新状态提示与缓存逻辑 7. 修复测试用例中的断言与请求方式问题,适配httpx删除请求特性 8. 重构后端路由的依赖注入,移除冗余的数据库会话依赖 9. 调整测试用例的权限校验逻辑,修正强制登出的权限判断 10. 修复语义分块测试的numpy依赖问题,清理冗余导入
230 lines
6.7 KiB
Python
230 lines
6.7 KiB
Python
"""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, UTC
|
|
|
|
import pytest
|
|
from yuxi.channels.contract.dtos.dashboard import (
|
|
AccountStats,
|
|
ChannelDeliveryStat,
|
|
ChannelRealtimeStat,
|
|
DashboardDeliveryQuery,
|
|
DashboardDeliveryResult,
|
|
DashboardOverviewQuery,
|
|
DashboardTodoResult,
|
|
RealtimeMetricsResult,
|
|
RealtimeQuery,
|
|
)
|
|
from yuxi.channels.contract.errors import ValidationError
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
def _aware_dt() -> datetime:
|
|
return datetime(2024, 1, 1, 12, 0, 0, tzinfo=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=UTC),
|
|
end_time=datetime(2024, 1, 1, tzinfo=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=UTC),
|
|
end_time=datetime(2024, 1, 1, tzinfo=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
|