ForcePilot/backend/test/unit/channels/contract/dtos/test_dtos.py
Kris b323922cdc style: 清理测试文件中的多余空行和导入顺序
本次提交清理了多个单元测试文件中的多余空行,调整了部分导入的顺序,同时修复了一处feishu错误翻译的导入顺序问题,移除了application/conftest.py中未使用的fake_report_repo fixture,优化代码格式提升可读性。
2026-07-06 20:52:20 +08:00

944 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""配置 / 传输 / 能力 DTO 单元测试。
覆盖 ``yuxi.channels.contract.dtos`` 模块下 config / transport / capability
三个子模块的 DTO 构造、默认值、不可变语义与 ``__post_init__`` 校验逻辑。
"""
from __future__ import annotations
import dataclasses
from datetime import datetime
from typing import Literal, get_args, get_origin
from unittest.mock import MagicMock
import pytest
from yuxi.channels.contract.dtos.capability import (
CapabilityResult,
ChannelCapabilities,
)
from yuxi.channels.contract.dtos.common import Operator, OperatorRole
from yuxi.channels.contract.dtos.config import (
BatchConfigUpdateSuccessItem,
BatchUpdateConfigCmd,
BatchUpdateConfigResult,
ConfigExportCmd,
ConfigExportResult,
ConfigField,
ConfigHistoryEntry,
ConfigHotReloadMode,
ConfigScope,
ConfigUpdateItem,
ConfigValue,
ConfigVersion,
ImportConfigCmd,
ImportConfigResult,
RollbackConfigCmd,
UpdateConfigCmd,
)
from yuxi.channels.contract.dtos.transport import (
PollingConfig,
PollResult,
StreamConfig,
StreamConnection,
TransportConfig,
TransportErrorCategory,
)
from yuxi.channels.contract.errors import ValidationError
pytestmark = pytest.mark.unit
def _make_operator() -> Operator:
"""构造测试用操作人。"""
return Operator(user_id="u-1", role=OperatorRole.ADMIN_USER)
# --------------------------------------------------------------------------- #
# config.py - 枚举
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestConfigHotReloadMode:
"""配置热更新模式枚举测试。"""
def test_enum_values_are_correct(self):
# Arrange
# Act
# Assert - 校验四个枚举成员的字符串值
assert ConfigHotReloadMode.OFF == "off"
assert ConfigHotReloadMode.HOT == "hot"
assert ConfigHotReloadMode.RESTART == "restart"
assert ConfigHotReloadMode.HYBRID == "hybrid"
def test_enum_inherits_from_str(self):
# Arrange
# Act
# Assert - StrEnum 应继承 str 并支持字符串比较
assert issubclass(ConfigHotReloadMode, str)
assert isinstance(ConfigHotReloadMode.HOT, str)
def test_enum_has_four_members(self):
# Arrange
# Act
members = list(ConfigHotReloadMode)
# Assert
assert len(members) == 4
@pytest.mark.unit
class TestConfigScope:
"""配置作用域枚举测试。"""
def test_enum_values_are_correct(self):
# Arrange
# Act
# Assert - 校验四个作用域枚举成员的字符串值
assert ConfigScope.GLOBAL == "global"
assert ConfigScope.CHANNEL == "channel"
assert ConfigScope.ACCOUNT == "account"
assert ConfigScope.PLUGIN == "plugin"
def test_enum_inherits_from_str(self):
# Arrange
# Act
# Assert - StrEnum 应继承 str 并支持字符串比较
assert issubclass(ConfigScope, str)
assert isinstance(ConfigScope.GLOBAL, str)
def test_enum_has_four_members(self):
# Arrange
# Act
members = list(ConfigScope)
# Assert
assert len(members) == 4
# --------------------------------------------------------------------------- #
# config.py - 配置版本与字段
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestConfigVersion:
"""配置版本 DTO 测试。"""
def test_fields_are_assigned(self):
# Arrange
now = datetime(2026, 1, 1, 12, 0, 0)
# Act
version = ConfigVersion(key="timeout", version=3, updated_at=now)
# Assert
assert version.key == "timeout"
assert version.version == 3
assert version.updated_at == now
def test_updated_at_accepts_none(self):
# Arrange
# Act
version = ConfigVersion(key="timeout", version=0, updated_at=None)
# Assert
assert version.updated_at is None
def test_is_frozen(self):
# Arrange
version = ConfigVersion(key="timeout", version=1, updated_at=None)
# Act / Assert - 冻结 dataclass 不允许修改属性
with pytest.raises(dataclasses.FrozenInstanceError):
version.version = 2 # type: ignore[misc]
@pytest.mark.unit
class TestConfigField:
"""配置字段元信息 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
# Act
field = ConfigField(key="timeout", type="int")
# Assert
assert field.key == "timeout"
assert field.type == "int"
def test_defaults_are_correct(self):
# Arrange
# Act
field = ConfigField(key="timeout", type="int")
# Assert - 校验默认值
assert field.required is False
assert field.default is None
assert field.hot_reloadable is True
assert field.sensitive is False
assert field.constraints is None
def test_custom_values_are_assigned(self):
# Arrange
constraints = {"min": 1, "max": 100}
# Act
field = ConfigField(
key="retry",
type="int",
required=True,
default=5,
hot_reloadable=False,
constraints=constraints,
)
# Assert
assert field.required is True
assert field.default == 5
assert field.hot_reloadable is False
assert field.constraints == constraints
def test_sensitive_field_is_assigned(self):
# Arrange / Act - F-01 sensitive 字段默认 False可显式声明 True
field = ConfigField(key="bot_token", type="str", required=True, sensitive=True)
# Assert
assert field.sensitive is True
def test_is_frozen(self):
# Arrange
field = ConfigField(key="timeout", type="int")
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
field.key = "other" # type: ignore[misc]
# --------------------------------------------------------------------------- #
# config.py - 更新 / 回滚命令
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestUpdateConfigCmd:
"""更新配置命令 DTO 测试。"""
def test_valid_construction_with_defaults(self):
# Arrange
operator = _make_operator()
# Act
cmd = UpdateConfigCmd(key="timeout", value=30, operator=operator)
# Assert - 默认作用域 GLOBALtarget 与 expected_version 为 None
assert cmd.key == "timeout"
assert cmd.value == 30
assert cmd.operator is operator
assert cmd.scope is ConfigScope.GLOBAL
assert cmd.target is None
assert cmd.expected_version is None
def test_valid_construction_with_all_fields(self):
# Arrange
operator = _make_operator()
# Act
cmd = UpdateConfigCmd(
key="timeout",
value=30,
operator=operator,
scope=ConfigScope.CHANNEL,
target="feishu",
expected_version=2,
)
# Assert
assert cmd.scope is ConfigScope.CHANNEL
assert cmd.target == "feishu"
assert cmd.expected_version == 2
def test_empty_key_raises_validation_error(self):
# Arrange
operator = _make_operator()
# Act / Assert - key 为空字符串应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
UpdateConfigCmd(key="", value=30, operator=operator)
assert exc_info.value.field == "key"
def test_non_positive_expected_version_raises(self):
# Arrange
operator = _make_operator()
# Act / Assert - expected_version <= 0 应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
UpdateConfigCmd(key="timeout", value=30, operator=operator, expected_version=0)
assert exc_info.value.field == "expected_version"
def test_target_required_for_channel_scope(self):
# Arrange
operator = _make_operator()
# Act / Assert - CHANNEL 作用域下 target 为空应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
UpdateConfigCmd(key="timeout", value=30, operator=operator, scope=ConfigScope.CHANNEL)
assert exc_info.value.field == "target"
def test_target_required_for_account_scope(self):
# Arrange
operator = _make_operator()
# Act / Assert - ACCOUNT 作用域下 target 为空应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
UpdateConfigCmd(key="timeout", value=30, operator=operator, scope=ConfigScope.ACCOUNT)
assert exc_info.value.field == "target"
def test_target_required_for_plugin_scope(self):
# Arrange
operator = _make_operator()
# Act / Assert - PLUGIN 作用域下 target 为空应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
UpdateConfigCmd(key="timeout", value=30, operator=operator, scope=ConfigScope.PLUGIN)
assert exc_info.value.field == "target"
def test_is_frozen(self):
# Arrange
operator = _make_operator()
cmd = UpdateConfigCmd(key="timeout", value=30, operator=operator)
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
cmd.value = 60 # type: ignore[misc]
@pytest.mark.unit
class TestRollbackConfigCmd:
"""回滚配置命令 DTO 测试。"""
def test_valid_construction_with_defaults(self):
# Arrange
operator = _make_operator()
# Act
cmd = RollbackConfigCmd(key="timeout", target_version=1, operator=operator)
# Assert
assert cmd.key == "timeout"
assert cmd.target_version == 1
assert cmd.operator is operator
assert cmd.scope is ConfigScope.GLOBAL
assert cmd.target is None
def test_empty_key_raises_validation_error(self):
# Arrange
operator = _make_operator()
# Act / Assert - key 为空字符串应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
RollbackConfigCmd(key="", target_version=1, operator=operator)
assert exc_info.value.field == "key"
def test_non_positive_target_version_raises(self):
# Arrange
operator = _make_operator()
# Act / Assert - target_version <= 0 应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
RollbackConfigCmd(key="timeout", target_version=0, operator=operator)
assert exc_info.value.field == "target_version"
def test_target_required_for_channel_scope(self):
# Arrange
operator = _make_operator()
# Act / Assert - CHANNEL 作用域下 target 为空应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
RollbackConfigCmd(
key="timeout",
target_version=1,
operator=operator,
scope=ConfigScope.CHANNEL,
)
assert exc_info.value.field == "target"
def test_target_required_for_account_scope(self):
# Arrange
operator = _make_operator()
# Act / Assert - ACCOUNT 作用域下 target 为空应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
RollbackConfigCmd(
key="timeout",
target_version=1,
operator=operator,
scope=ConfigScope.ACCOUNT,
)
assert exc_info.value.field == "target"
def test_target_required_for_plugin_scope(self):
# Arrange
operator = _make_operator()
# Act / Assert - PLUGIN 作用域下 target 为空应抛出 ValidationError
with pytest.raises(ValidationError) as exc_info:
RollbackConfigCmd(
key="timeout",
target_version=1,
operator=operator,
scope=ConfigScope.PLUGIN,
)
assert exc_info.value.field == "target"
def test_is_frozen(self):
# Arrange
operator = _make_operator()
cmd = RollbackConfigCmd(key="timeout", target_version=1, operator=operator)
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
cmd.target_version = 2 # type: ignore[misc]
# --------------------------------------------------------------------------- #
# config.py - 配置值与历史
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestConfigValue:
"""配置值 DTO 测试。"""
def test_fields_are_assigned(self):
# Arrange
# Act
value = ConfigValue(
key="timeout",
value=30,
version=2,
scope=ConfigScope.GLOBAL,
)
# Assert
assert value.key == "timeout"
assert value.value == 30
assert value.version == 2
assert value.scope is ConfigScope.GLOBAL
def test_is_frozen(self):
# Arrange
value = ConfigValue(key="timeout", value=30, version=2, scope=ConfigScope.GLOBAL)
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
value.value = 60 # type: ignore[misc]
@pytest.mark.unit
class TestConfigHistoryEntry:
"""配置历史版本条目 DTO 测试。"""
def test_fields_are_assigned(self):
# Arrange
now = datetime(2026, 1, 1, 12, 0, 0)
# Act
entry = ConfigHistoryEntry(version=2, value=30, updated_at=now)
# Assert
assert entry.version == 2
assert entry.value == 30
assert entry.updated_at == now
def test_updated_at_accepts_none(self):
# Arrange
# Act
entry = ConfigHistoryEntry(version=1, value="v", updated_at=None)
# Assert - 历史数据未存储时间戳时为 None
assert entry.updated_at is None
# --------------------------------------------------------------------------- #
# config.py - 导入 / 导出
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestConfigExportCmd:
"""配置导出命令 DTO 测试。"""
def test_defaults_are_correct(self):
# Arrange
operator = _make_operator()
# Act
cmd = ConfigExportCmd(operator=operator)
# Assert
assert cmd.operator is operator
assert cmd.scope is ConfigScope.GLOBAL
assert cmd.target is None
def test_custom_scope_and_target(self):
# Arrange
operator = _make_operator()
# Act
cmd = ConfigExportCmd(operator=operator, scope=ConfigScope.CHANNEL, target="feishu")
# Assert
assert cmd.scope is ConfigScope.CHANNEL
assert cmd.target == "feishu"
@pytest.mark.unit
class TestConfigExportResult:
"""配置导出结果 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
now = datetime(2026, 1, 1, 12, 0, 0)
data = {"timeout": 30}
# Act
result = ConfigExportResult(
scope=ConfigScope.GLOBAL,
config_data=data,
version=1,
exported_at=now,
)
# Assert
assert result.scope is ConfigScope.GLOBAL
assert result.config_data == data
assert result.version == 1
assert result.exported_at == now
assert result.target is None
def test_target_is_optional(self):
# Arrange
now = datetime(2026, 1, 1, 12, 0, 0)
# Act
result = ConfigExportResult(
scope=ConfigScope.CHANNEL,
config_data={},
version=1,
exported_at=now,
target="feishu",
)
# Assert
assert result.target == "feishu"
@pytest.mark.unit
class TestImportConfigCmd:
"""配置导入命令 DTO 测试。"""
def test_defaults_are_correct(self):
# Arrange
operator = _make_operator()
data = {"timeout": 30}
# Act
cmd = ImportConfigCmd(operator=operator, config_data=data)
# Assert
assert cmd.operator is operator
assert cmd.config_data == data
assert cmd.scope is ConfigScope.GLOBAL
assert cmd.target is None
assert cmd.overwrite is False
assert cmd.dry_run is False
def test_custom_flags(self):
# Arrange
operator = _make_operator()
# Act
cmd = ImportConfigCmd(
operator=operator,
config_data={},
scope=ConfigScope.CHANNEL,
target="feishu",
overwrite=True,
dry_run=True,
)
# Assert
assert cmd.scope is ConfigScope.CHANNEL
assert cmd.target == "feishu"
assert cmd.overwrite is True
assert cmd.dry_run is True
@pytest.mark.unit
class TestImportConfigResult:
"""配置导入结果 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
now = datetime(2026, 1, 1, 12, 0, 0)
failed_keys = ("bad_key", "missing_key")
# Act
result = ImportConfigResult(
imported_count=5,
skipped_count=2,
failed_count=2,
failed_keys=failed_keys,
imported_at=now,
)
# Assert
assert result.imported_count == 5
assert result.skipped_count == 2
assert result.failed_count == 2
assert result.failed_keys == failed_keys
assert result.imported_at == now
def test_failed_keys_is_tuple(self):
# Arrange
now = datetime(2026, 1, 1, 12, 0, 0)
# Act
result = ImportConfigResult(
imported_count=0,
skipped_count=0,
failed_count=0,
failed_keys=(),
imported_at=now,
)
# Assert - failed_keys 支持空元组
assert result.failed_keys == ()
# --------------------------------------------------------------------------- #
# config.py - 批量更新
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestConfigUpdateItem:
"""单条配置更新项 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
# Act
item = ConfigUpdateItem(key="timeout", value=30)
# Assert
assert item.key == "timeout"
assert item.value == 30
assert item.expected_version is None
def test_expected_version_can_be_set(self):
# Arrange
# Act
item = ConfigUpdateItem(key="timeout", value=30, expected_version=2)
# Assert
assert item.expected_version == 2
@pytest.mark.unit
class TestBatchConfigUpdateSuccessItem:
"""批量配置更新成功条目 DTO 测试。"""
def test_fields_are_assigned(self):
# Arrange
# Act
item = BatchConfigUpdateSuccessItem(key="timeout", new_version=3)
# Assert
assert item.key == "timeout"
assert item.new_version == 3
@pytest.mark.unit
class TestBatchUpdateConfigCmd:
"""批量更新配置命令 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
operator = _make_operator()
updates = (ConfigUpdateItem(key="timeout", value=30),)
# Act
cmd = BatchUpdateConfigCmd(operator=operator, updates=updates)
# Assert
assert cmd.operator is operator
assert cmd.updates == updates
assert cmd.scope is ConfigScope.GLOBAL
assert cmd.target is None
assert cmd.stop_on_error is False
def test_custom_values_are_assigned(self):
# Arrange
operator = _make_operator()
updates = (ConfigUpdateItem(key="a", value=1), ConfigUpdateItem(key="b", value=2))
# Act
cmd = BatchUpdateConfigCmd(
operator=operator,
updates=updates,
scope=ConfigScope.CHANNEL,
target="feishu",
stop_on_error=True,
)
# Assert
assert cmd.scope is ConfigScope.CHANNEL
assert cmd.target == "feishu"
assert cmd.stop_on_error is True
assert len(cmd.updates) == 2
@pytest.mark.unit
class TestBatchUpdateConfigResult:
"""批量更新配置结果 DTO 测试。"""
def test_fields_are_assigned(self):
# Arrange
succeeded = (BatchConfigUpdateSuccessItem(key="timeout", new_version=2),)
failed: tuple = ()
# Act
result = BatchUpdateConfigResult(total=1, succeeded=succeeded, failed=failed)
# Assert
assert result.total == 1
assert result.succeeded == succeeded
assert result.failed == failed
def test_empty_result(self):
# Arrange
# Act
result = BatchUpdateConfigResult(total=0, succeeded=(), failed=())
# Assert
assert result.total == 0
assert result.succeeded == ()
assert result.failed == ()
# --------------------------------------------------------------------------- #
# transport.py
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestTransportErrorCategory:
"""传输错误分类类型测试。"""
def test_is_literal_type(self):
# Arrange
# Act
origin = get_origin(TransportErrorCategory)
# Assert - 应为 Literal 类型别名
assert origin is Literal
def test_has_four_values(self):
# Arrange
# Act
args = get_args(TransportErrorCategory)
# Assert
assert args == ("auth_expired", "rate_limited", "transient", "permanent")
assert len(args) == 4
@pytest.mark.unit
class TestPollingConfig:
"""轮询模式配置 DTO 测试。"""
def test_defaults_are_correct(self):
# Arrange
# Act
config = PollingConfig()
# Assert
assert config.long_poll_timeout_ms == 0
assert config.poll_interval_ms == 0
assert config.max_batch_size == 100
def test_custom_values_are_assigned(self):
# Arrange
# Act
config = PollingConfig(long_poll_timeout_ms=5000, poll_interval_ms=1000, max_batch_size=50)
# Assert
assert config.long_poll_timeout_ms == 5000
assert config.poll_interval_ms == 1000
assert config.max_batch_size == 50
@pytest.mark.unit
class TestStreamConfig:
"""流式连接配置 DTO 测试。"""
def test_defaults_are_correct(self):
# Arrange
# Act
config = StreamConfig()
# Assert
assert config.heartbeat_interval_ms == 30000
assert config.connect_timeout_ms == 10000
def test_custom_values_are_assigned(self):
# Arrange
# Act
config = StreamConfig(heartbeat_interval_ms=15000, connect_timeout_ms=5000)
# Assert
assert config.heartbeat_interval_ms == 15000
assert config.connect_timeout_ms == 5000
@pytest.mark.unit
class TestTransportConfig:
"""传输层全局配置 DTO 测试。"""
def test_defaults_are_correct(self):
# Arrange
# Act
config = TransportConfig()
# Assert
assert config.stall_timeout_ms == 120000
assert config.backoff_schedule == (1.0, 2.0, 5.0, 10.0, 30.0)
assert config.backoff_jitter == 0.2
assert config.max_restart_attempts is None
assert config.graceful_shutdown_timeout_s == 10.0
def test_backoff_schedule_is_tuple(self):
# Arrange
# Act
config = TransportConfig()
# Assert - backoff_schedule 应为 tuple 保证不可变
assert isinstance(config.backoff_schedule, tuple)
def test_custom_values_are_assigned(self):
# Arrange
# Act
config = TransportConfig(
stall_timeout_ms=60000,
backoff_schedule=(1.0, 2.0),
backoff_jitter=0.5,
max_restart_attempts=3,
graceful_shutdown_timeout_s=30.0,
)
# Assert
assert config.stall_timeout_ms == 60000
assert config.backoff_schedule == (1.0, 2.0)
assert config.backoff_jitter == 0.5
assert config.max_restart_attempts == 3
assert config.graceful_shutdown_timeout_s == 30.0
@pytest.mark.unit
class TestPollResult:
"""轮询结果 DTO 测试。"""
def test_defaults_are_correct(self):
# Arrange
# Act
result = PollResult()
# Assert
assert result.messages == ()
assert result.next_cursor is None
assert result.error is None
def test_custom_values_are_assigned(self):
# Arrange
messages = ({"id": "1"}, {"id": "2"})
# Act
result = PollResult(messages=messages, next_cursor="cursor-1")
# Assert
assert result.messages == messages
assert result.next_cursor == "cursor-1"
assert result.error is None
def test_messages_is_tuple(self):
# Arrange
# Act
result = PollResult(messages=({"id": "1"},))
# Assert
assert isinstance(result.messages, tuple)
@pytest.mark.unit
class TestStreamConnection:
"""流式连接句柄 DTO 测试。"""
def test_required_callable_fields_are_assigned(self):
# Arrange
receive = MagicMock()
send = MagicMock()
close = MagicMock()
# Act
conn = StreamConnection(receive=receive, send=send, close=close)
# Assert
assert conn.receive is receive
assert conn.send is send
assert conn.close is close
def test_extra_defaults_to_none(self):
# Arrange
receive = MagicMock()
send = MagicMock()
close = MagicMock()
# Act
conn = StreamConnection(receive=receive, send=send, close=close)
# Assert
assert conn.extra is None
def test_extra_can_be_set(self):
# Arrange
receive = MagicMock()
send = MagicMock()
close = MagicMock()
# Act
conn = StreamConnection(receive=receive, send=send, close=close, extra={"session": "s1"})
# Assert
assert conn.extra == {"session": "s1"}
# --------------------------------------------------------------------------- #
# capability.py
# --------------------------------------------------------------------------- #
@pytest.mark.unit
class TestChannelCapabilities:
"""渠道能力集合 DTO 测试。"""
def test_all_defaults_are_correct(self):
# Arrange
# Act
caps = ChannelCapabilities()
# Assert - 所有布尔能力默认 False
assert caps.rich_message is False
assert caps.streaming is False
assert caps.typing_indicator is False
assert caps.message_edit is False
assert caps.message_recall is False
assert caps.supports_reaction is False
assert caps.supports_pin is False
assert caps.supports_card_update is False
assert caps.supports_card_update_streaming is False
assert caps.supports_image_inbound is False
assert caps.supports_video_inbound is False
assert caps.supports_image_outbound is False
assert caps.supports_video_outbound is False
assert caps.mention is False
assert caps.command is False
assert caps.directory is False
assert caps.doctor is False
assert caps.whitelist is False
assert caps.wizard is False
assert caps.tools is False
assert caps.status is False
assert caps.probeable is False
assert caps.identity_resolver is False
assert caps.lifecycle is False
assert caps.supports_qr_login is False
assert caps.agent_collaboration is False
def test_custom_values_are_assigned(self):
# Arrange
# Act
caps = ChannelCapabilities(
rich_message=True,
streaming=True,
wizard=True,
tools=True,
supports_card_update_streaming=True,
)
# Assert
assert caps.rich_message is True
assert caps.streaming is True
assert caps.wizard is True
assert caps.tools is True
assert caps.supports_card_update_streaming is True
def test_is_frozen(self):
# Arrange
caps = ChannelCapabilities()
# Act / Assert - 冻结 dataclass 不允许修改属性
with pytest.raises(dataclasses.FrozenInstanceError):
caps.rich_message = True # type: ignore[misc]
@pytest.mark.unit
class TestCapabilityResult:
"""能力探测结果 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
# Act
result = CapabilityResult(capability="streaming", declared=True)
# Assert
assert result.capability == "streaming"
assert result.declared is True
def test_defaults_are_correct(self):
# Arrange
# Act
result = CapabilityResult(capability="streaming", declared=True)
# Assert - proven 与 degraded 默认 False
assert result.proven is False
assert result.degraded is False
def test_custom_values_are_assigned(self):
# Arrange
# Act
result = CapabilityResult(
capability="streaming",
declared=True,
proven=True,
degraded=False,
)
# Assert
assert result.proven is True
assert result.degraded is False