1. 清理测试文件中未使用的导入与冗余代码 2. 修复审计日志与批量操作的空值约束,统一填充"global"作为默认渠道 3. 调整批量消息撤回的响应语义,对齐其他端点的部分成功契约 4. 修复访问规则批量克隆的唯一约束问题,新增后缀自动处理逻辑 5. 替换anyio为asyncio并行调用,修正时间UTC导入路径 6. 优化前端外部系统概览页的刷新状态提示与缓存逻辑 7. 修复测试用例中的断言与请求方式问题,适配httpx删除请求特性 8. 重构后端路由的依赖注入,移除冗余的数据库会话依赖 9. 调整测试用例的权限校验逻辑,修正强制登出的权限判断 10. 修复语义分块测试的numpy依赖问题,清理冗余导入
221 lines
6.3 KiB
Python
221 lines
6.3 KiB
Python
"""plugin.py DTO 单元测试。
|
|
|
|
覆盖 ``Permission`` / ``EnvVar`` / ``DomainEvent`` / ``PluginSummary`` /
|
|
``InstallPluginCmd`` / ``InstallPluginResult`` / ``UninstallPluginResult`` /
|
|
``PluginConfigResult`` / ``UpdatePluginConfigCmd`` /
|
|
``BatchPluginLifecycleCmd`` / ``BatchPluginLifecycleResult`` 的字段赋值、
|
|
默认值、不可变语义与 ``__post_init__`` 校验逻辑。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
from yuxi.channels.contract.dtos.common import Operator, OperatorRole
|
|
from yuxi.channels.contract.dtos.plugin import (
|
|
BatchPluginLifecycleCmd,
|
|
DomainEvent,
|
|
EnvVar,
|
|
InstallPluginCmd,
|
|
Permission,
|
|
PluginSummary,
|
|
UpdatePluginConfigCmd,
|
|
)
|
|
from yuxi.channels.contract.errors import ValidationError
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
def _make_operator() -> Operator:
|
|
return Operator(user_id="admin-1", role=OperatorRole.SUPERADMIN_USER)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPermission:
|
|
"""权限等级枚举测试。"""
|
|
|
|
def test_str_values(self):
|
|
assert Permission.REQUIRED_USER == "required_user"
|
|
assert Permission.ADMIN_USER == "admin_user"
|
|
assert Permission.SUPERADMIN_USER == "superadmin_user"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestEnvVar:
|
|
"""环境变量声明 DTO 测试。"""
|
|
|
|
def test_defaults(self):
|
|
# Act
|
|
var = EnvVar(name="API_KEY", description="API key")
|
|
# Assert
|
|
assert var.required is True
|
|
assert var.default is None
|
|
assert var.sensitive is False
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestDomainEvent:
|
|
"""领域事件 DTO 测试。"""
|
|
|
|
def test_fields_are_assigned_with_defaults(self):
|
|
# Act
|
|
event = DomainEvent(
|
|
event_id="evt-1",
|
|
event_type="PluginLoaded",
|
|
payload={"key": "value"},
|
|
timestamp=datetime(2024, 1, 1),
|
|
)
|
|
# Assert
|
|
assert event.trace_id is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPluginSummary:
|
|
"""插件摘要 DTO 测试。"""
|
|
|
|
def test_fields_are_assigned(self):
|
|
# Act
|
|
summary = PluginSummary(
|
|
plugin_id="wechat-plugin",
|
|
name="WeChat Plugin",
|
|
version="1.0.0",
|
|
channel_type="wechat",
|
|
state="started",
|
|
)
|
|
# Assert
|
|
assert summary.plugin_id == "wechat-plugin"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestInstallPluginCmd:
|
|
"""安装插件命令 DTO 测试。"""
|
|
|
|
def test_valid_construction(self):
|
|
# Act
|
|
cmd = InstallPluginCmd(
|
|
operator=_make_operator(),
|
|
source_type="path",
|
|
source="/plugins/wechat",
|
|
)
|
|
# Assert
|
|
assert cmd.version is None
|
|
assert cmd.force is False
|
|
|
|
def test_empty_source_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
InstallPluginCmd(
|
|
operator=_make_operator(),
|
|
source_type="path",
|
|
source="",
|
|
)
|
|
assert exc_info.value.field == "source"
|
|
|
|
def test_invalid_source_type_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
InstallPluginCmd(
|
|
operator=_make_operator(),
|
|
source_type="invalid", # type: ignore[arg-type]
|
|
source="/path",
|
|
)
|
|
assert exc_info.value.field == "source_type"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestUpdatePluginConfigCmd:
|
|
"""更新插件配置命令 DTO 测试。"""
|
|
|
|
def test_valid_construction(self):
|
|
# Act
|
|
cmd = UpdatePluginConfigCmd(
|
|
plugin_id="wechat-plugin",
|
|
operator=_make_operator(),
|
|
config={"key": "value"},
|
|
apply_mode="hot",
|
|
)
|
|
# Assert
|
|
assert cmd.account_id is None
|
|
|
|
def test_empty_plugin_id_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
UpdatePluginConfigCmd(
|
|
plugin_id="",
|
|
operator=_make_operator(),
|
|
config={"k": "v"},
|
|
apply_mode="hot",
|
|
)
|
|
assert exc_info.value.field == "plugin_id"
|
|
|
|
def test_empty_config_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
UpdatePluginConfigCmd(
|
|
plugin_id="p1",
|
|
operator=_make_operator(),
|
|
config={},
|
|
apply_mode="hot",
|
|
)
|
|
assert exc_info.value.field == "config"
|
|
|
|
def test_invalid_apply_mode_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
UpdatePluginConfigCmd(
|
|
plugin_id="p1",
|
|
operator=_make_operator(),
|
|
config={"k": "v"},
|
|
apply_mode="invalid", # type: ignore[arg-type]
|
|
)
|
|
assert exc_info.value.field == "apply_mode"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestBatchPluginLifecycleCmd:
|
|
"""批量插件生命周期命令 DTO 测试。"""
|
|
|
|
def test_valid_with_plugin_ids(self):
|
|
# Act
|
|
cmd = BatchPluginLifecycleCmd(
|
|
operator=_make_operator(),
|
|
plugin_ids=("p1", "p2"),
|
|
)
|
|
# Assert
|
|
assert cmd.filter is None
|
|
|
|
def test_valid_with_filter(self):
|
|
# Act
|
|
cmd = BatchPluginLifecycleCmd(
|
|
operator=_make_operator(),
|
|
filter={"channel_type": "wechat"},
|
|
)
|
|
# Assert
|
|
assert cmd.plugin_ids == ()
|
|
|
|
def test_both_empty_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
BatchPluginLifecycleCmd(operator=_make_operator())
|
|
assert exc_info.value.field == "plugin_ids"
|
|
|
|
def test_both_provided_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
BatchPluginLifecycleCmd(
|
|
operator=_make_operator(),
|
|
plugin_ids=("p1",),
|
|
filter={"state": "stopped"},
|
|
)
|
|
assert exc_info.value.field == "plugin_ids"
|
|
|
|
def test_empty_string_in_plugin_ids_raises(self):
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
BatchPluginLifecycleCmd(
|
|
operator=_make_operator(),
|
|
plugin_ids=("p1", ""),
|
|
)
|
|
assert exc_info.value.field == "plugin_ids"
|