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

253 lines
8.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
feedback-issue 目标仓库路由测试。
"""
from __future__ import annotations
import importlib
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
from app.runtime.config import settings
SCRIPT_DIR = Path(__file__).resolve().parents[1] / "skills" / "feedback-issue" / "scripts"
# 只在导入独立 Skill 脚本时扩展搜索路径,避免影响后续测试的模块解析。
with patch.object(sys, "path", [str(SCRIPT_DIR), *sys.path]):
common = importlib.import_module("feedback_issue_common")
prepare_script = importlib.import_module("prepare_feedback_issue")
submit_script = importlib.import_module("submit_feedback_issue")
class _FakeResponse:
"""
提交脚本使用的最小响应替身。
"""
def __init__(self, status_code: int, payload: dict | None = None):
"""
保存响应状态码和 JSON 数据。
"""
self.status_code = status_code
self._payload = payload or {}
self.headers = {}
self.text = ""
def json(self) -> dict:
"""
返回预设 JSON 数据。
"""
return self._payload
@pytest.fixture
def isolated_feedback_runtime():
"""
隔离 feedback-issue 脚本运行目录和 GitHub Token。
"""
with tempfile.TemporaryDirectory() as tmpdir:
config_backup = settings.CONFIG_DIR
token_backup = settings.GITHUB_TOKEN
settings.CONFIG_DIR = tmpdir
settings.GITHUB_TOKEN = None
settings.LOG_PATH.mkdir(parents=True, exist_ok=True)
try:
yield
finally:
settings.CONFIG_DIR = config_backup
settings.GITHUB_TOKEN = token_backup
@pytest.fixture
def diagnostics_file(isolated_feedback_runtime) -> Path:
"""
创建一份可复用的脚本诊断文件。
"""
file_path = common.runtime_file("diagnostics", ".json")
common.write_json_file(
file_path,
{
"original_user_request": "插件执行时报错,帮我提交 issue",
"found": True,
"logs": "ERROR plugin failed",
"doctor": {
"success": True,
"report": {
"status": "ok",
"summary": {"total": 1, "error": 0, "warn": 0, "fixed": 0},
"environment": {"runtime": "Docker"},
"findings": [],
},
},
"source_files": [str(settings.LOG_PATH / "plugins" / "demo.log")],
},
)
return file_path
def _valid_plugin_draft(diagnostics_file: Path, target_repo: str = "owner/MoviePilot-Plugins") -> dict:
"""
构造一份插件问题草稿。
"""
return {
"title": "[错误报告]: 插件定时任务执行时报错退出",
"version": "v2.12.2",
"environment": "Docker",
"issue_type": "插件问题",
"target_repo": target_repo,
"original_user_request": "插件执行时报错,帮我提交 issue",
"diagnostics_file": str(diagnostics_file),
"description": (
"## 现象\n"
"- DemoPlugin 的定时任务执行时报错退出。\n\n"
"## 复现步骤\n"
"1. 启用 DemoPlugin。\n"
"2. 等待定时任务触发。\n"
"3. 插件日志出现异常并停止执行。\n\n"
"## 期望行为\n"
"- 插件定时任务应正常执行,不影响主程序运行。\n\n"
"## 已定位 / 推测\n"
"- 仅插件日志出现异常,主程序 doctor 未发现错误。\n\n"
"## 已尝试的处理\n"
"- 重启插件后仍可复现。"
),
}
def _valid_feature_draft(diagnostics_file: Path) -> dict:
"""
构造一份功能请求草稿。
"""
return {
"title": "[功能请求]: 支持按插件来源仓库批量筛选",
"version": "v2.12.2",
"environment": "Docker",
"issue_type": "功能请求",
"target_repo": common.FEEDBACK_REPO,
"original_user_request": "希望支持按插件来源仓库批量筛选,帮我提功能请求",
"diagnostics_file": str(diagnostics_file),
"description": (
"## 需求背景\n"
"- 插件较多时,当前列表难以快速区分来源仓库。\n\n"
"## 使用场景\n"
"1. 管理员打开插件列表。\n"
"2. 希望只查看某个来源仓库安装的插件。\n"
"3. 需要快速定位同一仓库下的插件更新状态。\n\n"
"## 期望能力\n"
"- 支持按插件来源仓库筛选和批量查看插件。"
),
}
def test_plugin_issue_requires_non_main_target_repo(diagnostics_file):
"""
插件问题没有指定插件仓库时应拒绝,避免误投主仓库。
"""
draft = _valid_plugin_draft(diagnostics_file, target_repo=common.FEEDBACK_REPO)
draft_file = common.runtime_file("draft", ".json")
common.write_json_file(draft_file, draft)
result = prepare_script.prepare_issue(draft_file)
assert result["success"] is False
assert result["reason"] == "invalid_draft"
assert "插件所属 GitHub 仓库" in result["message"]
def test_plugin_prefill_url_targets_plugin_repository(diagnostics_file):
"""
插件问题的手动预填链接应指向插件仓库。
"""
draft_file = common.runtime_file("draft", ".json")
common.write_json_file(draft_file, _valid_plugin_draft(diagnostics_file))
prepared = prepare_script.prepare_issue(draft_file)
result = submit_script.submit_issue(prepared["payload_file"], username="admin")
assert result["success"] is False
assert result["reason"] == "no_token"
assert result["repo"] == "owner/MoviePilot-Plugins"
assert result["prefill_url"].startswith("https://github.com/owner/MoviePilot-Plugins/issues/new")
def test_plugin_api_submit_targets_plugin_repository(diagnostics_file):
"""
自动提交插件问题时 GitHub API 应调用插件仓库地址。
"""
settings.GITHUB_TOKEN = "ghp_test_token"
draft_file = common.runtime_file("draft", ".json")
common.write_json_file(draft_file, _valid_plugin_draft(diagnostics_file))
prepared = prepare_script.prepare_issue(draft_file)
with patch(
"submit_feedback_issue.RequestUtils.post",
return_value=_FakeResponse(
201,
{
"number": 12,
"html_url": "https://github.com/owner/MoviePilot-Plugins/issues/12",
},
),
) as post:
result = submit_script.submit_issue(prepared["payload_file"], username="admin")
assert result["success"] is True
assert result["repo"] == "owner/MoviePilot-Plugins"
assert post.call_args.args[0] == "https://api.github.com/repos/owner/MoviePilot-Plugins/issues"
assert "labels" not in post.call_args.kwargs["json"]
def test_feature_request_uses_feature_label(diagnostics_file):
"""
功能请求自动提交时应使用 feature request 标签而不是 bug。
"""
settings.GITHUB_TOKEN = "ghp_test_token"
draft_file = common.runtime_file("draft", ".json")
common.write_json_file(draft_file, _valid_feature_draft(diagnostics_file))
prepared = prepare_script.prepare_issue(draft_file)
with patch(
"submit_feedback_issue.RequestUtils.post",
return_value=_FakeResponse(
201,
{
"number": 13,
"html_url": "https://github.com/jxxghp/MoviePilot/issues/13",
},
),
) as post:
result = submit_script.submit_issue(prepared["payload_file"], username="admin")
assert result["success"] is True
assert post.call_args.kwargs["json"]["labels"] == ["feature request"]
@pytest.mark.parametrize("issue_type", ["主程序运行问题", "功能请求"])
def test_cli_environment_survives_preview_and_submission(diagnostics_file, issue_type):
"""
CLI 草稿通过预览、提交校验及模拟 GitHub API,不在中途被环境枚举拒绝。
"""
settings.GITHUB_TOKEN = "ghp_test_token"
draft = _valid_feature_draft(diagnostics_file)
if issue_type == "主程序运行问题":
draft = _valid_plugin_draft(diagnostics_file, target_repo=common.FEEDBACK_REPO)
draft.update(environment="CLI", issue_type=issue_type)
draft_file = common.runtime_file("draft", ".json")
common.write_json_file(draft_file, draft)
prepared = prepare_script.prepare_issue(draft_file)
assert prepared["success"] is True
assert "环境:CLI" in Path(prepared["preview_file"]).read_text(encoding="utf-8")
with patch(
"submit_feedback_issue.RequestUtils.post",
return_value=_FakeResponse(201, {"number": 14, "html_url": "https://github.com/jxxghp/MoviePilot/issues/14"}),
) as post:
result = submit_script.submit_issue(prepared["payload_file"], username="admin")
assert result["success"] is True
assert "### 运行环境\n\nCLI" in post.call_args.kwargs["json"]["body"]