fix: 兼容插件仪表盘空返回

This commit is contained in:
jxxghp
2026-06-13 22:54:35 +08:00
parent 3a233014de
commit ed1e31d379
3 changed files with 73 additions and 2 deletions

View File

@@ -513,7 +513,7 @@ def plugin_dashboard(
plugin_id: str,
user_agent: Annotated[str | None, Header()] = None,
_: User = Depends(get_current_active_superuser),
) -> schemas.PluginDashboard:
) -> Optional[schemas.PluginDashboard]:
"""
根据插件ID获取插件仪表板
"""

View File

@@ -1080,7 +1080,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
logger.error(f"获取插件[{plugin_id}]仪表盘元数据出错:{str(e)}")
return dashboard_meta
def get_plugin_dashboard(self, pid: str, key: str, user_agent: str = None) -> schemas.PluginDashboard:
def get_plugin_dashboard(self, pid: str, key: str, user_agent: str = None) -> Optional[schemas.PluginDashboard]:
"""
获取插件仪表盘
"""
@@ -1113,6 +1113,12 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
logger.error(f"插件 {pid} 调用方法 get_dashboard 出错: {str(e)}")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"插件 {pid} 调用方法 get_dashboard 出错: {str(e)}")
if dashboard is None:
return None
if not isinstance(dashboard, (tuple, list)) or len(dashboard) != 3:
logger.error(f"插件 {pid} 返回的仪表盘数据格式错误")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"插件 {pid} 返回的仪表盘数据格式错误")
cols, attrs, elements = dashboard
return schemas.PluginDashboard(
id=pid,

View File

@@ -0,0 +1,65 @@
from types import SimpleNamespace
from typing import Any, Iterator
import pytest
from fastapi import HTTPException
from app.core.plugin import PluginManager
from app.utils.singleton import Singleton
@pytest.fixture
def plugin_manager() -> Iterator[PluginManager]:
"""构造隔离的插件管理器实例,避免单例状态污染其它用例。"""
Singleton._instances.pop((PluginManager, (), frozenset()), None)
manager = PluginManager()
yield manager
Singleton._instances.pop((PluginManager, (), frozenset()), None)
def _plugin_with_dashboard(dashboard: Any) -> SimpleNamespace:
"""构造仅包含仪表板接口的插件实例。"""
return SimpleNamespace(
plugin_name="演示插件",
get_render_mode=lambda: ("vue", "dist/assets"),
get_dashboard=lambda key=None, user_agent=None: dashboard,
)
def test_plugin_dashboard_keeps_vue_elements_none(plugin_manager: PluginManager) -> None:
"""Vue 仪表板的 elements=None 应原样返回给前端渲染远程组件。"""
plugin_manager.running_plugins["DemoPlugin"] = _plugin_with_dashboard(
(
{"cols": 12},
{"title": "演示插件", "border": True},
None,
)
)
dashboard = plugin_manager.get_plugin_dashboard("DemoPlugin", "usage")
assert dashboard.id == "DemoPlugin"
assert dashboard.render_mode == "vue"
assert dashboard.cols == {"cols": 12}
assert dashboard.attrs == {"title": "演示插件", "border": True}
assert dashboard.elements is None
def test_plugin_dashboard_returns_none_when_plugin_has_no_dashboard(plugin_manager: PluginManager) -> None:
"""插件声明当前无仪表板时应返回 None而不是触发解包异常。"""
plugin_manager.running_plugins["DemoPlugin"] = _plugin_with_dashboard(None)
assert plugin_manager.get_plugin_dashboard("DemoPlugin", "missing") is None
def test_plugin_dashboard_rejects_invalid_dashboard_shape(plugin_manager: PluginManager) -> None:
"""非空但不符合三元组契约的仪表板数据应返回服务端错误。"""
plugin_manager.running_plugins["DemoPlugin"] = _plugin_with_dashboard(
{"cols": {}, "attrs": {}, "elements": []}
)
with pytest.raises(HTTPException) as exc_info:
plugin_manager.get_plugin_dashboard("DemoPlugin", "broken")
assert exc_info.value.status_code == 500
assert "仪表盘数据格式错误" in exc_info.value.detail