fix(storage): 修复通用管理契约下存储页报未知错误

- usage 动作返回的 StorageUsage pydantic 模型无法透过
  Response[Dict[str, Any]] 开放映射校验导致 500,统一转为 dict
- support_transtype 返回值恢复 transtype 包装结构,与前端期望一致
- 空结果(无用量/无整理方式)恢复成功空结构语义,
  不再因 bool(结果) 被前端客户端当作业务失败弹窗
- dashboard 用量聚合改为 dict 取值;新增响应校验与空结果回归测试
This commit is contained in:
jxxghp
2026-08-16 07:50:29 +08:00
parent 441ec9475e
commit 7d0cf76d9c
3 changed files with 41 additions and 12 deletions
+2 -2
View File
@@ -69,8 +69,8 @@ def _build_storage() -> schemas.Storage:
_result = StorageChain().manage_storage(storage=_storage, action=StorageAction.USAGE.value) _result = StorageChain().manage_storage(storage=_storage, action=StorageAction.USAGE.value)
_usage = _result.get("data") if _result.get("success") else None _usage = _result.get("data") if _result.get("success") else None
if _usage: if _usage:
total += _usage.total total += _usage.get("total") or 0
available += _usage.available available += _usage.get("available") or 0
return schemas.Storage(total_storage=total, used_storage=total - available) return schemas.Storage(total_storage=total, used_storage=total - available)
+5 -5
View File
@@ -13,7 +13,7 @@ from app.runtime.log import logger
from app.modules import _ModuleBase from app.modules import _ModuleBase
from app.modules.filemanager.storages import StorageBase from app.modules.filemanager.storages import StorageBase
from app.modules.filemanager.transhandler import TransHandler 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.schemas.types import MUSIC_ENTITY_ALBUM, MediaType, ModuleType, OtherModulesType, StorageAction
from app.adapters.system.host import SystemUtils from app.adapters.system.host import SystemUtils
from app.foundation import text as text_tools from app.foundation import text as text_tools
@@ -152,14 +152,14 @@ class FileManagerModule(_ModuleBase):
storage_oper = self.__get_storage_oper(storage) storage_oper = self.__get_storage_oper(storage)
if not storage_oper: if not storage_oper:
return {"success": False, "message": f"不支持 {storage} 的整理方式获取"} return {"success": False, "message": f"不支持 {storage} 的整理方式获取"}
transtype = storage_oper.support_transtype() # 与旧契约一致:返回值包装为 transtype,空结果同样返回成功空结构
return {"success": bool(transtype), "data": transtype} return {"success": True, "data": {"transtype": storage_oper.support_transtype() or {}}}
if action == StorageAction.USAGE: if action == StorageAction.USAGE:
storage_oper = self.__get_storage_oper(storage) storage_oper = self.__get_storage_oper(storage)
if not storage_oper: if not storage_oper:
return {"success": False, "message": f"不支持 {storage} 的存储使用情况"} return {"success": False, "message": f"不支持 {storage} 的存储使用情况"}
usage = storage_oper.usage() # 实现返回 pydantic 模型,转为 dict 后才能透过通用响应的开放映射校验
return {"success": bool(usage), "data": usage} return {"success": True, "data": (storage_oper.usage() or StorageUsage()).model_dump()}
# 登录类动作:存储实现不支持时返回失败信息 # 登录类动作:存储实现不支持时返回失败信息
oper_method = action.value oper_method = action.value
+34 -5
View File
@@ -7,6 +7,7 @@
3. 端点层的 ManageRequest 通用请求结构:target + action + params 3. 端点层的 ManageRequest 通用请求结构:target + action + params
""" """
from types import SimpleNamespace from types import SimpleNamespace
from typing import Any, Dict
import pytest 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): def test_storage_manage_usage_returns_oper_data(module):
"""usage 动作返回存储实现的用量数据""" """usage 动作返回纯 dict 用量数据,空结果同样成功"""
result = module.storage_manage(storage="fakestore", action="usage") result = module.storage_manage(storage="fakestore", action="usage")
assert result["success"] is True assert result["success"] is True
assert result["data"].total == 100 assert result["data"] == {"total": 100.0, "available": 40.0}
assert result["data"].available == 40
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): def test_storage_manage_support_transtype(module):
"""support_transtype 动作返回存储支持的整理方式""" """support_transtype 动作返回值包装为 transtype,与旧契约结构一致"""
result = module.storage_manage(storage="fakestore", action="support_transtype") result = module.storage_manage(storage="fakestore", action="support_transtype")
assert result["success"] is True 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): def test_storage_manage_login_action_forwards_params(module):