diff --git a/app/core/plugin.py b/app/core/plugin.py index 2959eb24..09c9b362 100644 --- a/app/core/plugin.py +++ b/app/core/plugin.py @@ -1552,8 +1552,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton): if plugin_info.get("icon"): plugin.plugin_icon = plugin_info.get("icon") # 标签 - if plugin_info.get("labels"): - plugin.plugin_label = plugin_info.get("labels") + plugin.plugin_label = self._normalize_plugin_label(plugin_info.get("labels")) # 作者 if plugin_info.get("author"): plugin.plugin_author = plugin_info.get("author") @@ -1571,6 +1570,22 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton): return plugin + @staticmethod + def _normalize_plugin_label(labels: Any) -> Optional[str]: + """ + 规整插件市场标签字段,兼容旧字符串和新列表格式。 + + :param labels: 插件市场 package 中的 labels 字段 + :return: 用空格拼接后的标签字符串,无法识别或为空时返回 None + """ + if isinstance(labels, str): + label = labels.strip() + return label or None + if isinstance(labels, list): + normalized_labels = [str(item).strip() for item in labels if str(item).strip()] + return " ".join(normalized_labels) or None + return None + async def async_get_online_plugins(self, force: bool = False) -> List[schemas.Plugin]: """ 异步获取所有在线插件信息 diff --git a/tests/test_plugin_helper.py b/tests/test_plugin_helper.py index 9c76442a..72e6b9be 100644 --- a/tests/test_plugin_helper.py +++ b/tests/test_plugin_helper.py @@ -495,6 +495,39 @@ class TestPluginHelper: assert [item["version"] for item in cached] == ["1.2.3"] assert responses == [] + def test_get_plugins_from_market_normalizes_list_labels(self, monkeypatch) -> None: + """ + 插件市场 labels 为列表时应转换为字符串,避免响应模型序列化异常。 + """ + try: + from app.core.plugin import PluginManager + from app.helper.plugin import PluginHelper + except ModuleNotFoundError as exc: + pytest.skip(f"missing dependency: {exc}") + + market_plugins = { + "DemoPlugin": { + "name": "Demo Plugin", + "description": "Demo", + "version": "1.0.0", + "labels": ["站点", "通知", ""], + "v2": True, + } + } + plugin_manager = PluginManager() + monkeypatch.setattr(plugin_manager, "_plugins", {}) + monkeypatch.setattr(plugin_manager, "_running_plugins", {}) + monkeypatch.setattr("app.core.plugin.settings", SimpleNamespace(VERSION_FLAG="v2")) + monkeypatch.setattr("app.core.plugin.SystemConfigOper", lambda: SimpleNamespace(get=lambda _key: [])) + monkeypatch.setattr("app.core.plugin.SitesHelper", lambda: SimpleNamespace(auth_level=1)) + monkeypatch.setattr(PluginHelper, "get_plugins", lambda _self, *_args: market_plugins) + + plugins = plugin_manager.get_plugins_from_market(REPO_URL) + + assert len(plugins) == 1 + assert plugins[0].plugin_label == "站点 通知" + assert plugins[0].model_dump()["plugin_label"] == "站点 通知" + def test_get_online_plugins_force_keeps_release_cache_scoped(self, monkeypatch): """ 全市场刷新不清理 Release 缓存,Release 接口按请求仓库协调刷新两类数据。