"""plugin.py DTO 单元测试。 覆盖 ``Permission`` / ``EnvVar`` / ``DomainEvent`` / ``PluginSummary`` / ``InstallPluginCmd`` / ``InstallPluginResult`` / ``UninstallPluginResult`` / ``PluginConfigResult`` / ``UpdatePluginConfigCmd`` / ``BatchPluginLifecycleCmd`` / ``BatchPluginLifecycleResult`` 的字段赋值、 默认值、不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses from datetime import datetime import pytest from yuxi.channels.contract.dtos.common import Operator, OperatorRole from yuxi.channels.contract.dtos.plugin import ( BatchPluginLifecycleCmd, BatchPluginLifecycleResult, BatchPluginLifecycleSuccessItem, DomainEvent, EnvVar, InstallPluginCmd, InstallPluginResult, Permission, PluginConfigResult, PluginSummary, UninstallPluginResult, 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"