From 65907328d2bc71f39664e0a9659fa4c722501a68 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 13 Sep 2026 20:04:47 +0800 Subject: [PATCH] fix(plugin): skip invalid installed IDs during startup --- app/startup/initializers/plugins.py | 16 +++++++++------- tests/test_plugin_monitor_lifecycle.py | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/app/startup/initializers/plugins.py b/app/startup/initializers/plugins.py index 3c192712d..5e24c6ce7 100644 --- a/app/startup/initializers/plugins.py +++ b/app/startup/initializers/plugins.py @@ -916,13 +916,15 @@ def _plugin_source_id(plugin_manager: PluginManager, plugin_id: str) -> str: def _local_plugin_sources(plugin_manager: PluginManager) -> set[str]: """返回安装清单中存在本地仓候选的物理插件身份。""" - installed = { - normalize_physical_plugin_id(plugin_id) - for plugin_id in ( - get_configured_system_config().get(SystemConfigKey.UserInstalledPlugins) - or [] - ) - } + installed: set[str] = set() + for plugin_id in ( + get_configured_system_config().get(SystemConfigKey.UserInstalledPlugins) + or [] + ): + try: + installed.add(normalize_physical_plugin_id(plugin_id)) + except ValueError as error: + logger.warning("跳过无效的已安装插件 ID %s:%s", plugin_id, error) candidates: set[str] = set() for plugin in plugin_manager.get_local_repo_plugins(): try: diff --git a/tests/test_plugin_monitor_lifecycle.py b/tests/test_plugin_monitor_lifecycle.py index 6ab2ef844..99b0b73e4 100644 --- a/tests/test_plugin_monitor_lifecycle.py +++ b/tests/test_plugin_monitor_lifecycle.py @@ -140,6 +140,32 @@ def test_init_plugins_defers_installed_local_candidate_until_sync(monkeypatch) - assert order == ["OnlineOnly"] +def test_init_plugins_skips_invalid_installed_plugin_id(monkeypatch) -> None: + """历史安装清单含不合规 ID 时,不能阻断其他插件的启动。""" + manager = MagicMock() + manager.classify_plugins.return_value = PluginDependencyClassification( + ready=("ReadyPlugin",), + missing_dependencies=(), + missing_source=(), + ) + manager.reopen_plugins.return_value = True + manager.get_local_repo_plugins.return_value = [] + manager.get_plugin_source_id.side_effect = lambda plugin_id: plugin_id + config = MagicMock() + config.get.return_value = ["legacy-plugin", "ReadyPlugin"] + monkeypatch.setattr(plugins_initializer, "PluginManager", lambda: manager) + monkeypatch.setattr( + plugins_initializer, + "get_configured_system_config", + lambda: config, + ) + monkeypatch.setattr(plugins_initializer, "register_plugin_api", MagicMock()) + + plugins_initializer.init_plugins() + + manager.start.assert_called_once_with("ReadyPlugin") + + def test_plugin_manager_projects_dependency_classification_to_runtime_status() -> None: """真实管理器按分类字段写入三类启动状态,避免测试替身掩盖字段漂移。""" _reset_plugin_manager()