From 7d0cf76d9c14ae5ea7881e0094cac69f2ef22a04 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 16 Aug 2026 07:50:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(storage):=20=E4=BF=AE=E5=A4=8D=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E7=AE=A1=E7=90=86=E5=A5=91=E7=BA=A6=E4=B8=8B=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E9=A1=B5=E6=8A=A5=E6=9C=AA=E7=9F=A5=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - usage 动作返回的 StorageUsage pydantic 模型无法透过 Response[Dict[str, Any]] 开放映射校验导致 500,统一转为 dict - support_transtype 返回值恢复 transtype 包装结构,与前端期望一致 - 空结果(无用量/无整理方式)恢复成功空结构语义, 不再因 bool(结果) 被前端客户端当作业务失败弹窗 - dashboard 用量聚合改为 dict 取值;新增响应校验与空结果回归测试 --- app/api/endpoints/dashboard.py | 4 +-- app/modules/filemanager/__init__.py | 10 ++++---- tests/test_storage_manage.py | 39 +++++++++++++++++++++++++---- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/app/api/endpoints/dashboard.py b/app/api/endpoints/dashboard.py index b233d0de0..91a8fae4b 100644 --- a/app/api/endpoints/dashboard.py +++ b/app/api/endpoints/dashboard.py @@ -69,8 +69,8 @@ def _build_storage() -> schemas.Storage: _result = StorageChain().manage_storage(storage=_storage, action=StorageAction.USAGE.value) _usage = _result.get("data") if _result.get("success") else None if _usage: - total += _usage.total - available += _usage.available + total += _usage.get("total") or 0 + available += _usage.get("available") or 0 return schemas.Storage(total_storage=total, used_storage=total - available) diff --git a/app/modules/filemanager/__init__.py b/app/modules/filemanager/__init__.py index 895d30298..1a26a0cf9 100644 --- a/app/modules/filemanager/__init__.py +++ b/app/modules/filemanager/__init__.py @@ -13,7 +13,7 @@ from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.filemanager.storages import StorageBase from app.modules.filemanager.transhandler import TransHandler -from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, TransferDirectoryConf, FileItem +from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, TransferDirectoryConf, FileItem, StorageUsage from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaType, ModuleType, OtherModulesType, StorageAction from app.adapters.system.host import SystemUtils from app.foundation import text as text_tools @@ -152,14 +152,14 @@ class FileManagerModule(_ModuleBase): storage_oper = self.__get_storage_oper(storage) if not storage_oper: return {"success": False, "message": f"不支持 {storage} 的整理方式获取"} - transtype = storage_oper.support_transtype() - return {"success": bool(transtype), "data": transtype} + # 与旧契约一致:返回值包装为 transtype,空结果同样返回成功空结构 + return {"success": True, "data": {"transtype": storage_oper.support_transtype() or {}}} if action == StorageAction.USAGE: storage_oper = self.__get_storage_oper(storage) if not storage_oper: return {"success": False, "message": f"不支持 {storage} 的存储使用情况"} - usage = storage_oper.usage() - return {"success": bool(usage), "data": usage} + # 实现返回 pydantic 模型,转为 dict 后才能透过通用响应的开放映射校验 + return {"success": True, "data": (storage_oper.usage() or StorageUsage()).model_dump()} # 登录类动作:存储实现不支持时返回失败信息 oper_method = action.value diff --git a/tests/test_storage_manage.py b/tests/test_storage_manage.py index d804e4deb..aaeb481bc 100644 --- a/tests/test_storage_manage.py +++ b/tests/test_storage_manage.py @@ -7,6 +7,7 @@ 3. 端点层的 ManageRequest 通用请求结构:target + action + params """ from types import SimpleNamespace +from typing import Any, Dict import pytest @@ -105,18 +106,46 @@ def test_storage_manage_save_config_passes_conf_through(module): def test_storage_manage_usage_returns_oper_data(module): - """usage 动作返回存储实现的用量数据。""" + """usage 动作返回纯 dict 用量数据,空结果同样成功。""" result = module.storage_manage(storage="fakestore", action="usage") assert result["success"] is True - assert result["data"].total == 100 - assert result["data"].available == 40 + assert result["data"] == {"total": 100.0, "available": 40.0} + + +def test_storage_manage_usage_data_passes_open_mapping_response(module): + """用量数据必须能透过通用响应 Response[Dict[str, Any]] 的开放映射校验。 + + 回归守护:存储实现返回 pydantic 模型时若未转 dict, + 端点响应校验会直接 500,前端存储页整体报未知错误。 + """ + result = module.storage_manage(storage="fakestore", action="usage") + response = schemas.Response[Dict[str, Any]]( + success=result["success"], message=result.get("message"), data=result["data"] + ) + assert response.data == {"total": 100.0, "available": 40.0} + + +def test_storage_manage_usage_defaults_when_oper_returns_none(module, monkeypatch): + """存储实现查不到用量时返回成功的默认空结构而非业务失败。""" + monkeypatch.setattr(_FakeStorageOper, "usage", lambda self: None) + result = module.storage_manage(storage="fakestore", action="usage") + assert result["success"] is True + assert result["data"] == {"total": 0.0, "available": 0.0} def test_storage_manage_support_transtype(module): - """support_transtype 动作返回存储支持的整理方式。""" + """support_transtype 动作返回值包装为 transtype,与旧契约结构一致。""" result = module.storage_manage(storage="fakestore", action="support_transtype") assert result["success"] is True - assert result["data"] == {"move": True} + assert result["data"] == {"transtype": {"move": True}} + + +def test_storage_manage_support_transtype_empty_still_succeeds(module, monkeypatch): + """存储不支持任何整理方式时返回成功空结构,避免前端把空结果当业务失败。""" + monkeypatch.setattr(_FakeStorageOper, "support_transtype", lambda self: {}) + result = module.storage_manage(storage="fakestore", action="support_transtype") + assert result["success"] is True + assert result["data"] == {"transtype": {}} def test_storage_manage_login_action_forwards_params(module):