ForcePilot/backend/test/unit/channels/plugins/dingtalk/test_entry.py
Kris 532e7c59ec test: add and fix multi-channel unit tests
1. 新增QQBot、Wecom、Dingtalk、Instagram、WechatMp等渠道的单元测试夹具与测试用例
2. 修复WechatILink的缓存键顺序与测试用例
3. 补全Feishu适配器的日志断言与测试逻辑
4. 清理WechatILink冗余的clear_context_token测试代码
2026-07-08 22:59:24 +08:00

218 lines
7.9 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.

"""dingtalk entry 模块单元测试。
覆盖 ``channel_entry(host, manifest)`` 入口函数F-03 单真相源签名):
- 适配器注册17 个适配器,均通过 registerAdapter 注册)
- 生命周期钩子注册
- ``PluginManifest`` 返回值manifest 直接复用入参adapters 为运行时清单)
F-03 单真相源改造后,``entry.py`` 不再构造声明性字段,所有声明性字段由
discover 阶段解析的 ``ChannelManifest`` 承载。本测试通过
``load_manifest_from_dir`` 加载真实 ``manifest.json`` 作为入参。
entry.py 源码 docstring 描述"17 个适配器16 列表 + 1 单实例)"
但实际 ``identity_resolver`` 同样通过 ``registerAdapter`` 注册,故
``registerAdapter`` 调用次数 == ``_ADAPTER_TYPES`` 长度 == 17。
"""
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,
PluginManifest,
)
from yuxi.channels.plugins.dingtalk.entry import (
_ADAPTER_TYPES,
channel_entry,
)
from yuxi.channels.plugins.dingtalk.lifecycle import DingTalkLifecycleHandler
pytestmark = pytest.mark.unit
# ------------------------------------------------------------------
# 测试夹具
# ------------------------------------------------------------------
@pytest.fixture
def dingtalk_manifest() -> ChannelManifest:
"""加载 dingtalk 插件真实 manifest.json 作为 entry 入参。"""
plugin_dir = os.path.join(
os.path.dirname(__file__),
"..",
"..",
"..",
"..",
"..",
"package",
"yuxi",
"channels",
"plugins",
"dingtalk",
)
return load_manifest_from_dir(os.path.abspath(plugin_dir))
# ------------------------------------------------------------------
# channel_entry
# ------------------------------------------------------------------
@pytest.mark.unit
class TestChannelEntry:
"""channel_entry 入口函数测试F-03 单真相源签名)。"""
def test_channel_entry_is_callable(self):
# Arrange / Act / Assert
assert callable(channel_entry)
def test_returns_plugin_manifest(self, fake_plugin_host, dingtalk_manifest):
# Act
result = channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert
assert isinstance(result, PluginManifest)
def test_returns_input_manifest_unchanged(self, fake_plugin_host, dingtalk_manifest):
"""F-03 单真相源:返回的 PluginManifest.manifest 应为入参 manifest 直接复用。"""
# Act
result = channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert
assert result.manifest is dingtalk_manifest
def test_registers_all_adapters(self, fake_plugin_host, dingtalk_manifest):
"""注册 17 个适配器(均通过 registerAdapter 注册)。"""
# Act
channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert
assert fake_plugin_host.registerAdapter.call_count == 17
adapter_types = [call.args[0] for call in fake_plugin_host.registerAdapter.call_args_list]
assert tuple(adapter_types) == _ADAPTER_TYPES
def test_adapter_types_length_is_17(self):
# Arrange / Act / Assert
assert len(_ADAPTER_TYPES) == 17
def test_adapters_contain_expected_names(self):
# Arrange
expected = {
"inbound",
"outbound",
"session",
"status",
"rich_message",
"streaming",
"mention",
"message_ops",
"directory",
"command",
"whitelist",
"wizard",
"doctor",
"lifecycle",
"probeable",
"identity_resolver",
"stream_connector",
}
# Act / Assert
assert set(_ADAPTER_TYPES) == expected
def test_no_login_adapter_registered(self, fake_plugin_host, dingtalk_manifest):
"""supports_qr_login=false不注册 login_adapter。"""
# Act
channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert
adapter_types = [call.args[0] for call in fake_plugin_host.registerAdapter.call_args_list]
assert "login" not in adapter_types
def test_registers_lifecycle_handler(self, fake_plugin_host, dingtalk_manifest):
# Act
channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert
fake_plugin_host.registerLifecycleHandler.assert_called_once()
handler = fake_plugin_host.registerLifecycleHandler.call_args.args[0]
assert isinstance(handler, DingTalkLifecycleHandler)
def test_manifest_adapters_match_registered_types(self, fake_plugin_host, dingtalk_manifest):
# Act
result = channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert
assert result.adapters == _ADAPTER_TYPES
assert len(result.adapters) == 17
def test_gets_required_ports(self, fake_plugin_host, dingtalk_manifest):
"""验证 entry 调用 getConfigPort/getLoggerPort/getCachePort/getPersistencePort。"""
# Act
channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert
fake_plugin_host.getConfigPort.assert_called_once()
fake_plugin_host.getLoggerPort.assert_called_once()
fake_plugin_host.getCachePort.assert_called_once()
fake_plugin_host.getPersistencePort.assert_called_once()
def test_handler_receives_manifest_config_schema(self, fake_plugin_host, dingtalk_manifest):
"""F-03 单真相源LifecycleHandler 接收 manifest.config_schema。"""
# Act
channel_entry(fake_plugin_host, dingtalk_manifest)
# Assert - entry 不会因 config_schema 缺失而失败
assert dingtalk_manifest.config_schema is not None
assert len(dingtalk_manifest.config_schema) > 0
# ------------------------------------------------------------------
# F-03 单真相源:验证 entry.py 不含硬编码声明
# ------------------------------------------------------------------
@pytest.mark.unit
class TestNoHardcodedDeclarations:
"""F-03 单真相源:验证 entry.py 不含已被删除的硬编码声明。"""
def test_no_accessible_ports_constant(self):
import yuxi.channels.plugins.dingtalk.entry as entry_mod
assert not hasattr(entry_mod, "_ACCESSIBLE_PORTS")
def test_no_injectable_pipelines_constant(self):
import yuxi.channels.plugins.dingtalk.entry as entry_mod
assert not hasattr(entry_mod, "_INJECTABLE_PIPELINES")
def test_no_resource_quota_constant(self):
import yuxi.channels.plugins.dingtalk.entry as entry_mod
assert not hasattr(entry_mod, "_RESOURCE_QUOTA")
def test_no_build_config_schema_function(self):
import yuxi.channels.plugins.dingtalk.entry as entry_mod
assert not hasattr(entry_mod, "_build_config_schema")
def test_source_no_channel_manifest_construction(self):
"""验证 entry.py 源码不含 ChannelManifest(...) 直接构造调用。"""
import inspect
from yuxi.channels.plugins.dingtalk import entry as entry_mod
source = inspect.getsource(entry_mod)
assert "ChannelManifest(" not in source
def test_source_no_channel_capabilities_construction(self):
"""验证 entry.py 源码不含 ChannelCapabilities(...) 直接构造调用。"""
import inspect
from yuxi.channels.plugins.dingtalk import entry as entry_mod
source = inspect.getsource(entry_mod)
assert "ChannelCapabilities(" not in source
def test_adapter_types_constant_preserved(self):
"""验证 _ADAPTER_TYPES 仍保留(运行时适配器清单,非声明性字段)。"""
import yuxi.channels.plugins.dingtalk.entry as entry_mod
assert hasattr(entry_mod, "_ADAPTER_TYPES")
assert len(entry_mod._ADAPTER_TYPES) == 17