"""wecom 插件 manifest.json 字段完整性测试。 验证 ``manifest.json`` 经 ``load_manifest_from_dir`` 加载后的关键不变量: - 渠道标识(``channel_type`` / ``provides``) - 能力声明(26 项 capabilities 字段) - 配置 schema(23 项 config_schema) - 可访问端口(13 项 accessible_ports) - 资源配额(``max_connections`` == 50) - 凭证策略(``credential_strategy.type`` == ``"hybrid"``) - 传输模式(``transport_mode`` == ``"both"``) - 可注入管道(``injectable_pipelines`` == ``["inbound", "outbound"]``) - 生命周期(8 项 lifecycle 钩子) 测试通过 ``load_manifest_from_dir`` 加载真实 ``manifest.json``,确保校验 基于实际文件内容而非硬编码预期值。 """ from __future__ import annotations import os import pytest from yuxi.channels.application.lifecycle.manifest_loader import load_manifest_from_dir from yuxi.channels.contract.plugin.manifest import ChannelManifest pytestmark = pytest.mark.unit @pytest.fixture def wecom_manifest() -> ChannelManifest: """加载 wecom 插件真实 manifest.json。""" plugin_dir = os.path.join( os.path.dirname(__file__), "..", "..", "..", "..", "..", "package", "yuxi", "channels", "plugins", "wecom", ) return load_manifest_from_dir(os.path.abspath(plugin_dir)) # ------------------------------------------------------------------ # 加载与渠道标识 # ------------------------------------------------------------------ @pytest.mark.unit class TestManifestLoading: """manifest.json 加载与渠道标识测试。""" def test_load_manifest_does_not_raise(self, wecom_manifest) -> None: # Act / Assert - 加载不抛异常即可(fixture 已完成加载) assert wecom_manifest is not None def test_channel_type_is_wecom(self, wecom_manifest) -> None: assert wecom_manifest.channel_type == "wecom" def test_provides_contains_wecom(self, wecom_manifest) -> None: assert list(wecom_manifest.provides) == ["wecom"] # ------------------------------------------------------------------ # 能力声明(26 项) # ------------------------------------------------------------------ @pytest.mark.unit class TestCapabilities: """capabilities 字段完整性测试(26 项)。""" def test_capabilities_has_26_fields(self, wecom_manifest) -> None: """验证 capabilities 含 26 项能力字段。""" from dataclasses import fields cap_fields = [f.name for f in fields(wecom_manifest.capabilities)] assert len(cap_fields) == 26 def test_rich_message_true(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.rich_message is True def test_streaming_false(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.streaming is False def test_message_recall_true(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.message_recall is True def test_supports_card_update_true(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.supports_card_update is True def test_supports_card_update_streaming_true(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.supports_card_update_streaming is True def test_supports_image_inbound_true(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.supports_image_inbound is True def test_supports_qr_login_true(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.supports_qr_login is True def test_identity_resolver_true(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.identity_resolver is True def test_agent_collaboration_false(self, wecom_manifest) -> None: assert wecom_manifest.capabilities.agent_collaboration is False # ------------------------------------------------------------------ # 配置 schema(23 项) # ------------------------------------------------------------------ @pytest.mark.unit class TestConfigSchema: """config_schema 字段完整性测试(23 项)。""" def test_config_schema_has_23_fields(self, wecom_manifest) -> None: assert len(wecom_manifest.config_schema) == 23 def test_required_keys_present(self, wecom_manifest) -> None: """验证必填配置键均在 schema 中声明。""" keys = {f.key for f in wecom_manifest.config_schema} for required_key in ("corp_id", "corp_secret", "agent_id", "encoding_aes_key", "token", "webhook_url"): assert required_key in keys def test_optional_secret_keys_present(self, wecom_manifest) -> None: """验证可选 Secret 配置键在 schema 中声明。""" keys = {f.key for f in wecom_manifest.config_schema} assert "external_contact_secret" in keys assert "kf_secret" in keys # ------------------------------------------------------------------ # accessible_ports(13 项) # ------------------------------------------------------------------ @pytest.mark.unit class TestAccessiblePorts: """accessible_ports 字段完整性测试(13 项)。""" def test_accessible_ports_has_13_items(self, wecom_manifest) -> None: assert len(wecom_manifest.accessible_ports) == 13 def test_core_ports_present(self, wecom_manifest) -> None: """验证核心端口在 accessible_ports 中声明。""" ports = set(wecom_manifest.accessible_ports) for port in ("ConfigPort", "LoggerPort", "CachePort", "PersistencePort"): assert port in ports # ------------------------------------------------------------------ # resource_quota # ------------------------------------------------------------------ @pytest.mark.unit class TestResourceQuota: """resource_quota 资源配额测试。""" def test_max_connections_is_50(self, wecom_manifest) -> None: assert wecom_manifest.resource_quota is not None assert wecom_manifest.resource_quota.max_connections == 50 # ------------------------------------------------------------------ # credential_strategy # ------------------------------------------------------------------ @pytest.mark.unit class TestCredentialStrategy: """credential_strategy 凭证策略测试。""" def test_type_is_hybrid(self, wecom_manifest) -> None: assert wecom_manifest.credential_strategy is not None assert wecom_manifest.credential_strategy.type.value == "hybrid" # ------------------------------------------------------------------ # transport_mode # ------------------------------------------------------------------ @pytest.mark.unit class TestTransportMode: """transport_mode 传输模式测试。""" def test_transport_mode_is_both(self, wecom_manifest) -> None: assert wecom_manifest.transport_mode == "both" # ------------------------------------------------------------------ # injectable_pipelines # ------------------------------------------------------------------ @pytest.mark.unit class TestInjectablePipelines: """injectable_pipelines 可注入管道测试。""" def test_injectable_pipelines_contains_inbound_outbound(self, wecom_manifest) -> None: assert list(wecom_manifest.injectable_pipelines) == ["inbound", "outbound"] # ------------------------------------------------------------------ # lifecycle(8 项) # ------------------------------------------------------------------ @pytest.mark.unit class TestLifecycle: """lifecycle 生命周期钩子测试。""" def test_lifecycle_has_8_hooks(self, wecom_manifest) -> None: assert len(wecom_manifest.lifecycle) == 8 def test_lifecycle_contains_all_hooks(self, wecom_manifest) -> None: """验证 8 个生命周期钩子全部声明。""" hooks = set(wecom_manifest.lifecycle) for hook in ("init", "start", "stop", "pause", "resume", "unload", "reconfigure", "fail"): assert hook in hooks