ForcePilot/backend/test/unit/channels/contract/dtos/test_pairing.py
Kris 08617091dc refactor: 整理项目包结构与导入路径
- 新增多个业务域的__init__.py模块文件,规范包导出结构
- 调整多个DTO文件的导入路径,统一模块组织方式
- 移除测试文件中多余的空行与导入语句
- 优化部分业务模块的包层级划分
2026-07-18 02:04:03 +08:00

214 lines
6.0 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
import pytest
from yuxi.channels.contract.dtos.messaging.common import Operator, OperatorRole
from yuxi.channels.contract.dtos.pairing.pairing import (
BatchPairingCmd,
BotLoopBudget,
CleanExpiredPairingsCmd,
DmDecision,
DmPolicy,
PairingId,
PairingQuery,
PairingRecord,
PairingStatsQuery,
PairingStatus,
)
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"