1. 删除了 test/unit/external_systems/framework/ 下的废弃空测试目录 2. 修复多处测试断言逻辑、参数传递与测试数据构造 3. 新增日志级别、缓存令牌、流事件等DTO单元测试 4. 补充路由绑定、会话仓储、outbox仓储的测试覆盖 5. 更新测试用例中的异常类型、参数校验与业务逻辑断言
219 lines
6.2 KiB
Python
219 lines
6.2 KiB
Python
"""pairing.py DTO 单元测试。
|
|
|
|
覆盖 ``PairingId`` / ``PairingStatus`` / ``DmDecision`` / ``DmPolicy`` /
|
|
``PairingRecord`` / ``PairingQuery`` / ``BotLoopBudget`` /
|
|
``CreatePairingResult`` / ``BatchPairingCmd`` / ``CleanExpiredPairingsCmd`` /
|
|
``PairingStatsQuery`` / ``PairingTrendPoint`` / ``PairingStatsResult`` 的
|
|
字段赋值、默认值、不可变语义与 ``__post_init__`` 校验逻辑。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
from yuxi.channels.contract.dtos.channel import ChannelType
|
|
from yuxi.channels.contract.dtos.common import Operator, OperatorRole
|
|
from yuxi.channels.contract.dtos.pairing import (
|
|
BatchPairingCmd,
|
|
BotLoopBudget,
|
|
CleanExpiredPairingsCmd,
|
|
CreatePairingResult,
|
|
DmDecision,
|
|
DmPolicy,
|
|
PairingId,
|
|
PairingQuery,
|
|
PairingRecord,
|
|
PairingStatsQuery,
|
|
PairingStatsResult,
|
|
PairingStatus,
|
|
PairingTrendPoint,
|
|
)
|
|
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 TestPairingId:
|
|
"""配对 ID DTO 测试。"""
|
|
|
|
def test_value_is_assigned(self):
|
|
# Act
|
|
pid = PairingId(value="pair-001")
|
|
# Assert
|
|
assert pid.value == "pair-001"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPairingStatus:
|
|
"""配对状态枚举测试。"""
|
|
|
|
def test_str_values(self):
|
|
assert PairingStatus.PENDING == "pending"
|
|
assert PairingStatus.APPROVED == "approved"
|
|
assert PairingStatus.REJECTED == "rejected"
|
|
assert PairingStatus.EXPIRED == "expired"
|
|
assert PairingStatus.REVOKED == "revoked"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestDmDecision:
|
|
"""DM 决策枚举测试。"""
|
|
|
|
def test_str_values(self):
|
|
assert DmDecision.ALLOW == "allow"
|
|
assert DmDecision.DENY == "deny"
|
|
assert DmDecision.PENDING_PAIRING == "pending_pairing"
|
|
assert DmDecision.WHITELIST == "whitelist"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestDmPolicy:
|
|
"""DM 策略枚举测试。"""
|
|
|
|
def test_str_values(self):
|
|
assert DmPolicy.ALLOW == "allow"
|
|
assert DmPolicy.DENY == "deny"
|
|
assert DmPolicy.PAIRING_REQUIRED == "pairing_required"
|
|
assert DmPolicy.WHITELIST == "whitelist"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPairingRecord:
|
|
"""配对记录 DTO 测试。"""
|
|
|
|
def test_fields_are_assigned_with_defaults(self):
|
|
# Act
|
|
record = PairingRecord(
|
|
pairing_id="pair-1",
|
|
channel_account_id="acc-1",
|
|
peer_id="peer-1",
|
|
status=PairingStatus.PENDING,
|
|
)
|
|
# Assert
|
|
assert record.channel_type is None
|
|
assert record.approver_id is None
|
|
assert record.version == 1
|
|
|
|
def test_is_frozen(self):
|
|
# Arrange
|
|
record = PairingRecord(
|
|
pairing_id="p1",
|
|
channel_account_id="a1",
|
|
peer_id="peer",
|
|
status=PairingStatus.PENDING,
|
|
)
|
|
# Act / Assert
|
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
record.status = PairingStatus.APPROVED # type: ignore[misc]
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPairingQuery:
|
|
"""配对查询 DTO 测试。"""
|
|
|
|
def test_defaults(self):
|
|
# Act
|
|
query = PairingQuery()
|
|
# Assert
|
|
assert query.limit == 100
|
|
assert query.offset == 0
|
|
assert query.status is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestBotLoopBudget:
|
|
"""机器人循环预算 DTO 测试。"""
|
|
|
|
def test_defaults(self):
|
|
# Act
|
|
budget = BotLoopBudget(max_replies_per_hour=100, cooldown_seconds=60)
|
|
# Assert
|
|
assert budget.current_count == 0
|
|
assert budget.last_reply_at is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestBatchPairingCmd:
|
|
"""批量配对命令 DTO 测试。"""
|
|
|
|
def test_valid_construction(self):
|
|
# Act
|
|
cmd = BatchPairingCmd(
|
|
pairing_ids=("pair-1", "pair-2"),
|
|
operator=_make_operator(),
|
|
)
|
|
# Assert
|
|
assert cmd.reason is None
|
|
|
|
def test_empty_pairing_ids_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
BatchPairingCmd(pairing_ids=(), operator=_make_operator())
|
|
assert exc_info.value.field == "pairing_ids"
|
|
|
|
def test_too_many_pairing_ids_raises(self):
|
|
# Arrange
|
|
ids = tuple(f"pair-{i}" for i in range(501))
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
BatchPairingCmd(pairing_ids=ids, operator=_make_operator())
|
|
assert exc_info.value.field == "pairing_ids"
|
|
|
|
def test_reason_too_long_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
BatchPairingCmd(
|
|
pairing_ids=("pair-1",),
|
|
operator=_make_operator(),
|
|
reason="x" * 513,
|
|
)
|
|
assert exc_info.value.field == "reason"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestCleanExpiredPairingsCmd:
|
|
"""清理过期配对命令 DTO 测试。"""
|
|
|
|
def test_defaults(self):
|
|
# Act
|
|
cmd = CleanExpiredPairingsCmd(operator=_make_operator())
|
|
# Assert
|
|
assert cmd.max_count == 500
|
|
assert cmd.channel_type is None
|
|
|
|
def test_max_count_below_min_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
CleanExpiredPairingsCmd(operator=_make_operator(), 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:
|
|
CleanExpiredPairingsCmd(operator=_make_operator(), max_count=1001)
|
|
assert exc_info.value.field == "max_count"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPairingStatsQuery:
|
|
"""配对统计查询 DTO 测试。"""
|
|
|
|
def test_defaults(self):
|
|
# Act
|
|
query = PairingStatsQuery()
|
|
# Assert
|
|
assert query.granularity == "day"
|
|
|
|
def test_invalid_granularity_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
PairingStatsQuery(granularity="minute")
|
|
assert exc_info.value.field == "granularity"
|