mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
refactor(api): 渠道与存储管理端点通用化,端点层零目标特色
- schemas 新增公共 ManageRequest(target+action+params) 与 StorageAction 词汇表
- endpoint 层收敛为 POST /notification/manage、/storage/manage 两个通用接口,
不再定义任何渠道/存储特定的名称、参数与响应字段,前端上送参数原样透传
- chain 层 manage_channel/manage_storage 接受字符串标识纯透明转发,
StorageChain 移除全部特色管理方法
- FileManagerModule 新增 storage_manage 统一入口,模块返回归一化为
{success, message, data};wechatclawbot channel_manage 同步归一化,
路由标识兼容枚举名/值/对象
- dashboard 与 agentopsassistant 的用量查询改走 manage_storage(usage)
- 新增 10 项存储契约守护测试,通知侧补充 3 项链透传与字符串路由测试,
API 守护测试对 ManageRequest 开放映射按设计豁免
This commit is contained in:
@@ -633,6 +633,10 @@ def test_openapi_success_models_have_no_implicit_empty_nested_schemas():
|
||||
# 分类规则与 CookieCloud 解密载荷按设计接受扩展键。
|
||||
"CategoryRule",
|
||||
"CookieDecryptedPayload",
|
||||
# 通用管理请求的 params 按设计透传模块个性化参数。
|
||||
"ManageRequest",
|
||||
# 通用管理响应的 data 为模块自定义结构,按设计不固定字段。
|
||||
"Response_Dict_str__Any__",
|
||||
}
|
||||
allowed_empty_components = {"McpJsonRpcEmptyResult"}
|
||||
violations = []
|
||||
|
||||
@@ -10,6 +10,7 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.chain.notification import NotificationChain
|
||||
from app.modules.wechatclawbot import WechatClawBotModule
|
||||
from app.schemas.types import MessageChannel, NotificationAction
|
||||
|
||||
@@ -95,7 +96,7 @@ def test_channel_manage_prefers_saved_instance(module, monkeypatch):
|
||||
source="已保存",
|
||||
)
|
||||
assert result["success"] is True
|
||||
assert result["connected"] is True
|
||||
assert result["data"]["connected"] is True
|
||||
|
||||
|
||||
def test_channel_manage_migrate_cache_dispatches_without_client(module, monkeypatch):
|
||||
@@ -118,3 +119,43 @@ def test_channel_manage_migrate_cache_dispatches_without_client(module, monkeypa
|
||||
new_name="新名",
|
||||
)
|
||||
assert result == {"success": True, "message": "迁移成功"}
|
||||
|
||||
|
||||
def test_notification_chain_forwards_target_action_and_params(monkeypatch):
|
||||
"""链层接受字符串标识与透传参数,按 channel_manage 契约原样转发。"""
|
||||
captured = {}
|
||||
|
||||
def fake_run_module(self, method, **kwargs):
|
||||
captured.update(method=method, kwargs=kwargs)
|
||||
return {"success": True, "data": {"connected": True}}
|
||||
|
||||
monkeypatch.setattr(NotificationChain, "run_module", fake_run_module)
|
||||
chain = NotificationChain.__new__(NotificationChain)
|
||||
result = chain.manage_channel(channel="WechatClawBot", action="status", source="预览")
|
||||
|
||||
assert captured["method"] == "channel_manage"
|
||||
assert captured["kwargs"] == {
|
||||
"channel": "WechatClawBot",
|
||||
"action": "status",
|
||||
"source": "预览",
|
||||
}
|
||||
assert result["success"] is True
|
||||
|
||||
|
||||
def test_notification_chain_reports_missing_module(monkeypatch):
|
||||
"""无模块实现 channel_manage 时返回统一失败结构。"""
|
||||
monkeypatch.setattr(NotificationChain, "run_module", lambda self, method, **kwargs: None)
|
||||
chain = NotificationChain.__new__(NotificationChain)
|
||||
result = chain.manage_channel(channel="unknown", action="status")
|
||||
assert result["success"] is False
|
||||
assert result["message"]
|
||||
|
||||
|
||||
def test_channel_manage_accepts_plain_string_identifiers(module, monkeypatch):
|
||||
"""端点层透传的原始字符串渠道名与动作名可被模块正确路由与解释。"""
|
||||
saved = SimpleNamespace()
|
||||
saved.test_connection = lambda: (True, None)
|
||||
monkeypatch.setattr(module, "get_instance", lambda name=None: saved)
|
||||
|
||||
result = module.channel_manage(channel="WechatClawBot", action="test_connection")
|
||||
assert result == {"success": True, "message": None}
|
||||
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
网盘存储通用管理契约(storage_manage)守护测试
|
||||
|
||||
验证与通知渠道一致的通用模式:
|
||||
1. 链层 manage_storage 只透明转发存储标识、动作与参数,不做任何存储特定处理
|
||||
2. 模块按存储标识路由,动作语义与参数解释封闭在模块内
|
||||
3. 端点层的 ManageRequest 通用请求结构:target + action + params
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app import schemas
|
||||
from app.chain.storage import StorageChain
|
||||
from app.modules.filemanager import FileManagerModule
|
||||
|
||||
|
||||
class _FakeStorageOper:
|
||||
"""记录管理动作调用情况的假存储实现"""
|
||||
|
||||
schema = SimpleNamespace(value="fakestore")
|
||||
calls = []
|
||||
|
||||
def set_config(self, conf):
|
||||
_FakeStorageOper.calls.append(("set_config", conf))
|
||||
|
||||
def reset_config(self):
|
||||
_FakeStorageOper.calls.append(("reset_config", None))
|
||||
|
||||
def usage(self):
|
||||
return schemas.StorageUsage(total=100, available=40)
|
||||
|
||||
def support_transtype(self):
|
||||
return {"move": True}
|
||||
|
||||
def check_login(self, **kwargs):
|
||||
_FakeStorageOper.calls.append(("check_login", kwargs))
|
||||
return {"status": True}, None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def module(monkeypatch):
|
||||
_FakeStorageOper.calls.clear()
|
||||
module = FileManagerModule()
|
||||
monkeypatch.setattr(module, "_support_storages", ["fakestore"])
|
||||
monkeypatch.setattr(module, "_storage_schemas", [_FakeStorageOper])
|
||||
return module
|
||||
|
||||
|
||||
def test_manage_request_schema():
|
||||
"""ManageRequest 仅定义目标标识、动作标识与透传参数,无任何特定领域字段。"""
|
||||
request = schemas.ManageRequest(target="fakestore", action="usage")
|
||||
assert request.target == "fakestore"
|
||||
assert request.action == "usage"
|
||||
assert request.params == {}
|
||||
|
||||
|
||||
def test_storage_chain_forwards_target_action_and_params(monkeypatch):
|
||||
"""链层按 storage_manage 契约原样透传,不引入存储特定逻辑。"""
|
||||
captured = {}
|
||||
|
||||
def fake_run_module(self, method, **kwargs):
|
||||
captured.update(method=method, kwargs=kwargs)
|
||||
return {"success": True, "data": {"total": 100}}
|
||||
|
||||
monkeypatch.setattr(StorageChain, "run_module", fake_run_module)
|
||||
chain = StorageChain.__new__(StorageChain)
|
||||
result = chain.manage_storage(storage="fakestore", action="usage", extra="value")
|
||||
|
||||
assert captured["method"] == "storage_manage"
|
||||
assert captured["kwargs"] == {"storage": "fakestore", "action": "usage", "extra": "value"}
|
||||
assert result["success"] is True
|
||||
|
||||
|
||||
def test_storage_chain_reports_missing_module(monkeypatch):
|
||||
"""无模块实现 storage_manage 时返回统一失败结构。"""
|
||||
monkeypatch.setattr(StorageChain, "run_module", lambda self, method, **kwargs: None)
|
||||
chain = StorageChain.__new__(StorageChain)
|
||||
result = chain.manage_storage(storage="unknown", action="usage")
|
||||
assert result["success"] is False
|
||||
assert result["message"]
|
||||
|
||||
|
||||
def test_storage_manage_rejects_unknown_action(module):
|
||||
"""动作词汇表之外的请求返回统一错误结构。"""
|
||||
result = module.storage_manage(storage="fakestore", action="not_an_action")
|
||||
assert result["success"] is False
|
||||
assert "不支持" in result["message"]
|
||||
|
||||
|
||||
def test_storage_manage_rejects_unknown_storage(module):
|
||||
"""未注册的存储标识直接返回错误,不进入动作分发。"""
|
||||
result = module.storage_manage(storage="unknown_store", action="usage")
|
||||
assert result["success"] is False
|
||||
assert "不支持的存储类型" in result["message"]
|
||||
|
||||
|
||||
def test_storage_manage_save_config_passes_conf_through(module):
|
||||
"""save_config 动作将 params.conf 原样交给存储实现持久化。"""
|
||||
result = module.storage_manage(
|
||||
storage="fakestore", action="save_config", conf={"token": "abc"}
|
||||
)
|
||||
assert result["success"] is True
|
||||
assert ("set_config", {"token": "abc"}) in _FakeStorageOper.calls
|
||||
|
||||
|
||||
def test_storage_manage_usage_returns_oper_data(module):
|
||||
"""usage 动作返回存储实现的用量数据。"""
|
||||
result = module.storage_manage(storage="fakestore", action="usage")
|
||||
assert result["success"] is True
|
||||
assert result["data"].total == 100
|
||||
assert result["data"].available == 40
|
||||
|
||||
|
||||
def test_storage_manage_support_transtype(module):
|
||||
"""support_transtype 动作返回存储支持的整理方式。"""
|
||||
result = module.storage_manage(storage="fakestore", action="support_transtype")
|
||||
assert result["success"] is True
|
||||
assert result["data"] == {"move": True}
|
||||
|
||||
|
||||
def test_storage_manage_login_action_forwards_params(module):
|
||||
"""登录类动作透传表单参数并归一化元组返回值。"""
|
||||
result = module.storage_manage(storage="fakestore", action="check_login", ck="ck1", t="t1")
|
||||
assert result["success"] is True
|
||||
assert result["data"] == {"status": True}
|
||||
assert ("check_login", {"ck": "ck1", "t": "t1"}) in _FakeStorageOper.calls
|
||||
|
||||
|
||||
def test_storage_manage_reports_unsupported_login_action(module):
|
||||
"""存储实现未提供对应登录方法时返回明确失败信息。"""
|
||||
result = module.storage_manage(storage="fakestore", action="generate_qrcode")
|
||||
assert result["success"] is False
|
||||
assert "不支持" in result["message"]
|
||||
Reference in New Issue
Block a user