"""entry 模块单元测试。 覆盖 ``channel_entry(host, manifest)`` 入口函数(F-03 单真相源新签名): - 适配器注册(18 个适配器) - 生命周期钩子注册 - ``PluginManifest`` 返回值(manifest 直接复用入参,adapters 为运行时清单) F-03 单真相源改造后,``entry.py`` 不再构造声明性字段(capabilities / config_schema / accessible_ports / injectable_pipelines / resource_quota / env_vars / credential_strategy 等),所有声明性字段由 discover 阶段解析的 ``ChannelManifest`` 承载。本测试通过 ``load_manifest_from_dir`` 加载真实 ``manifest.json`` 作为入参,验证 entry 函数仅做适配器实例化与注册。 """ from __future__ import annotations 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.feishu.entry import ( _ADAPTER_TYPES, channel_entry, ) from yuxi.channels.plugins.feishu.lifecycle import FeishuLifecycleHandler pytestmark = pytest.mark.unit # ------------------------------------------------------------------ # 测试夹具 # ------------------------------------------------------------------ @pytest.fixture def feishu_manifest() -> ChannelManifest: """加载 feishu 插件真实 manifest.json 作为 entry 入参。""" import os plugin_dir = os.path.join( os.path.dirname(__file__), "..", "..", "..", "..", "..", "package", "yuxi", "channels", "plugins", "feishu", ) return load_manifest_from_dir(os.path.abspath(plugin_dir)) # ------------------------------------------------------------------ # channel_entry # ------------------------------------------------------------------ @pytest.mark.unit class TestChannelEntry: """channel_entry 入口函数测试(F-03 单真相源新签名)。""" def test_returns_plugin_manifest(self, fake_plugin_host, feishu_manifest): # Act result = channel_entry(fake_plugin_host, feishu_manifest) # Assert assert isinstance(result, PluginManifest) def test_returns_input_manifest_unchanged(self, fake_plugin_host, feishu_manifest): """F-03 单真相源:返回的 PluginManifest.manifest 应为入参 manifest 直接复用。""" # Act result = channel_entry(fake_plugin_host, feishu_manifest) # Assert - manifest 直接复用入参,不重新构造 assert result.manifest is feishu_manifest def test_registers_all_18_adapters(self, fake_plugin_host, feishu_manifest): # Act channel_entry(fake_plugin_host, feishu_manifest) # Assert assert fake_plugin_host.registerAdapter.call_count == 18 adapter_types = [call.args[0] for call in fake_plugin_host.registerAdapter.call_args_list] assert tuple(adapter_types) == _ADAPTER_TYPES def test_registers_lifecycle_handler(self, fake_plugin_host, feishu_manifest): # Act channel_entry(fake_plugin_host, feishu_manifest) # Assert fake_plugin_host.registerLifecycleHandler.assert_called_once() handler = fake_plugin_host.registerLifecycleHandler.call_args.args[0] assert isinstance(handler, FeishuLifecycleHandler) def test_manifest_adapters_match_registered_types(self, fake_plugin_host, feishu_manifest): # Act result = channel_entry(fake_plugin_host, feishu_manifest) # Assert assert result.adapters == _ADAPTER_TYPES assert len(result.adapters) == 18 def test_gets_required_ports(self, fake_plugin_host, feishu_manifest): """验证 entry 调用 getConfigPort/getLoggerPort/getCachePort/getPersistencePort。""" # Act channel_entry(fake_plugin_host, feishu_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, feishu_manifest): """F-03 单真相源:LifecycleHandler 接收 manifest.config_schema 而非 _build_config_schema()。""" # Act channel_entry(fake_plugin_host, feishu_manifest) # Assert - 通过验证 manifest 的 config_schema 传递给 handler 间接验证 # handler 构造时第 4 个参数为 config_schema # 由于 _FakeHost 使用 MagicMock,handler 实例化时直接传入,无需额外断言 # 此测试验证 entry 不会因 config_schema 缺失而失败 assert feishu_manifest.config_schema is not None assert len(feishu_manifest.config_schema) > 0 # ------------------------------------------------------------------ # F-03 单真相源:验证 entry.py 不含硬编码声明 # ------------------------------------------------------------------ @pytest.mark.unit class TestNoHardcodedDeclarations: """F-03 单真相源:验证 entry.py 不含已被删除的硬编码声明。 entry.py 改造后应不再包含以下私有定义(声明性字段已收敛到 manifest.json): - ``_ACCESSIBLE_PORTS`` - ``_INJECTABLE_PIPELINES`` - ``_RESOURCE_QUOTA`` - ``_build_config_schema`` - ``_build_env_vars`` - ``ChannelManifest(...)`` 构造调用(manifest 应为入参复用) - ``ChannelCapabilities(...)`` 构造调用(能力声明应由 manifest 承载) """ def test_no_accessible_ports_constant(self): """验证 _ACCESSIBLE_PORTS 已被删除。""" import yuxi.channels.plugins.feishu.entry as entry_mod assert not hasattr(entry_mod, "_ACCESSIBLE_PORTS") def test_no_injectable_pipelines_constant(self): """验证 _INJECTABLE_PIPELINES 已被删除。""" import yuxi.channels.plugins.feishu.entry as entry_mod assert not hasattr(entry_mod, "_INJECTABLE_PIPELINES") def test_no_resource_quota_constant(self): """验证 _RESOURCE_QUOTA 已被删除。""" import yuxi.channels.plugins.feishu.entry as entry_mod assert not hasattr(entry_mod, "_RESOURCE_QUOTA") def test_no_build_config_schema_function(self): """验证 _build_config_schema 已被删除。""" import yuxi.channels.plugins.feishu.entry as entry_mod assert not hasattr(entry_mod, "_build_config_schema") def test_no_build_env_vars_function(self): """验证 _build_env_vars 已被删除。""" import yuxi.channels.plugins.feishu.entry as entry_mod assert not hasattr(entry_mod, "_build_env_vars") def test_source_no_channel_manifest_construction(self): """验证 entry.py 源码不含 ChannelManifest(...) 直接构造调用。""" import inspect from yuxi.channels.plugins.feishu import entry as entry_mod source = inspect.getsource(entry_mod) # 排除 import 语句与文档字符串中的提及 # entry.py 应通过 `manifest=manifest` 复用入参,而非重新构造 assert "ChannelManifest(" not in source def test_source_no_channel_capabilities_construction(self): """验证 entry.py 源码不含 ChannelCapabilities(...) 直接构造调用。""" import inspect from yuxi.channels.plugins.feishu import entry as entry_mod source = inspect.getsource(entry_mod) assert "ChannelCapabilities(" not in source def test_source_no_credential_strategy_construction(self): """验证 entry.py 源码不含 CredentialStrategy(...) 直接构造调用。""" import inspect from yuxi.channels.plugins.feishu import entry as entry_mod source = inspect.getsource(entry_mod) assert "CredentialStrategy(" not in source def test_source_no_resource_quota_construction(self): """验证 entry.py 源码不含 ResourceQuota(...) 直接构造调用。""" import inspect from yuxi.channels.plugins.feishu import entry as entry_mod source = inspect.getsource(entry_mod) assert "ResourceQuota(" not in source def test_adapter_types_constant_preserved(self): """验证 _ADAPTER_TYPES 仍保留(运行时适配器清单,非声明性字段)。""" import yuxi.channels.plugins.feishu.entry as entry_mod assert hasattr(entry_mod, "_ADAPTER_TYPES") assert len(entry_mod._ADAPTER_TYPES) == 18