Files
MoviePilot/tests/test_agent_doctor_tool.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

133 lines
4.2 KiB
Python

"""
验证只读诊断工具的注册、结果裁剪和 MCP 暴露契约。
"""
import asyncio
import json
from datetime import datetime
from unittest.mock import patch
from app.agent.tools.catalog import ToolCatalogSnapshot
from app.agent.tools.factory import MoviePilotToolFactory
from app.agent.tools.impl.query_doctor_report import QueryDoctorReportTool
from app.agent.tools.manager import MoviePilotToolsManager
from app.doctor.models import (
DoctorFinding,
DoctorFindingStatus,
DoctorReport,
DoctorSeverity,
)
def _doctor_report() -> DoctorReport:
"""
构造一份稳定的 doctor 测试报告。
"""
report = DoctorReport(
generated_at=datetime(2026, 6, 12, 12, 0, 0),
version="v2.test",
environment={
"runtime": "Docker",
"config_path": "/config",
"is_docker": True,
},
)
report.add_finding(
DoctorFinding(
id="logs.moviepilot.recent_errors",
severity=DoctorSeverity.Warn,
status=DoctorFindingStatus.Degraded,
title="最近日志存在错误线索",
detail="ERROR demo Cookie: <REDACTED>",
recommendation="结合前后的启动日志定位异常。",
context={"log_file": "/config/logs/moviepilot.log", "matches": 1},
)
)
return report
def test_factory_registers_doctor_report_tool():
"""
工具工厂应注册 doctor 诊断报告工具。
"""
with patch(
"app.agent.tools.factory._get_plugin_agent_tools",
return_value=[],
):
tools = MoviePilotToolFactory.create_tools(
session_id="doctor-session",
user_id="10001",
)
tool_names = {tool.name for tool in tools}
assert "query_doctor_report" in tool_names
def test_query_doctor_report_returns_readonly_report():
"""
doctor 工具应以只读方式返回结构化诊断报告。
"""
tool = QueryDoctorReportTool(session_id="doctor-session", user_id="10001")
with patch(
"app.agent.tools.impl.query_doctor_report.run_doctor",
return_value=_doctor_report(),
) as run_doctor:
result = asyncio.run(tool.run(deep=True))
payload = json.loads(result)
assert payload["success"] is True
assert payload["deep"] is True
assert payload["include_details"] is True
assert payload["report"]["status"] == "degraded"
assert payload["report"]["environment"]["runtime"] == "Docker"
assert payload["report"]["findings"][0]["detail"] == "ERROR demo Cookie: <REDACTED>"
assert payload["report"]["findings"][0]["affects_report_status"] is True
run_doctor.assert_called_once_with(deep=True)
def test_query_doctor_report_compact_mode_omits_details():
"""
紧凑模式应保留诊断项概要并省略 detail 和 context。
"""
tool = QueryDoctorReportTool(session_id="doctor-session", user_id="10001")
with patch(
"app.agent.tools.impl.query_doctor_report.run_doctor",
return_value=_doctor_report(),
):
result = asyncio.run(tool.run(include_details=False))
payload = json.loads(result)
finding = payload["report"]["findings"][0]
assert finding["id"] == "logs.moviepilot.recent_errors"
assert finding["title"] == "最近日志存在错误线索"
assert finding["affects_report_status"] is True
assert "detail" not in finding
assert "context" not in finding
def test_mcp_tool_manager_exposes_doctor_report_tool():
"""
MCP 工具管理器应暴露 doctor 诊断报告工具。
"""
tool = QueryDoctorReportTool(session_id="doctor-session", user_id="10001")
catalog = ToolCatalogSnapshot.from_tools(
[tool], plugin_revision=0, factory_revision="test"
)
with patch.object(
MoviePilotToolFactory,
"create_catalog",
return_value=catalog,
) as create_catalog:
manager = MoviePilotToolsManager(is_admin=True)
create_catalog.assert_not_called()
tool_definitions = manager.list_tools()
create_catalog.assert_called_once()
assert [item.name for item in tool_definitions] == ["query_doctor_report"]
schema = tool_definitions[0].input_schema
assert "deep" in schema["properties"]
assert "include_details" in schema["properties"]