1. 新增QQBot、Wecom、Dingtalk、Instagram、WechatMp等渠道的单元测试夹具与测试用例 2. 修复WechatILink的缓存键顺序与测试用例 3. 补全Feishu适配器的日志断言与测试逻辑 4. 清理WechatILink冗余的clear_context_token测试代码
227 lines
7.9 KiB
Python
227 lines
7.9 KiB
Python
"""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
|