From 6b1e5f14a606be8c774a3ab667c546e664fac76d Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Fri, 14 Aug 2026 01:04:49 +0800 Subject: [PATCH] fix(subscribeassistantenhanced): align v3 API response contract --- .github/scripts/check_plugin_versions.py | 29 ++------------ package.v3.json | 4 +- .../subscribeassistantenhanced/README.md | 1 + .../subscribeassistantenhanced/__init__.py | 4 +- tests/ci/test_plugin_release_gate.py | 39 +++++++++++++++---- .../test_dashboard.py | 24 +++++++++++- 6 files changed, 61 insertions(+), 40 deletions(-) diff --git a/.github/scripts/check_plugin_versions.py b/.github/scripts/check_plugin_versions.py index 07ccd988..5a9d0b67 100644 --- a/.github/scripts/check_plugin_versions.py +++ b/.github/scripts/check_plugin_versions.py @@ -60,16 +60,8 @@ def _version_parts(value: object) -> tuple[int, ...] | None: return tuple(int(part) for part in match.group().split(".")) -def _normalized_version(value: object, width: int = 4) -> tuple[int, ...] | None: - """补齐版本段用于大小比较。""" - parts = _version_parts(value) - if parts is None: - return None - return parts + (0,) * max(0, width - len(parts)) - - def _check_v3_metadata(path: Path, plugin_id: str, metadata: dict) -> list[str]: - """校验 V3 专用实现的系统版本、history 与旧版本迁移规则。""" + """校验 V3 专用实现长期有效的发布元数据约束。""" errors: list[str] = [] version = str(metadata.get("version") or "").strip() version_parts = _version_parts(version) @@ -83,10 +75,8 @@ def _check_v3_metadata(path: Path, plugin_id: str, metadata: dict) -> list[str]: expected_history_key = f"v{version}" if not isinstance(history, dict) or list(history) != [expected_history_key]: errors.append(f"{path}: {plugin_id} history 必须只保留当前版本 {expected_history_key}") - else: - expected_changelog = f'MoviePilot V3 版本{metadata.get("name", "")}插件' - if history[expected_history_key] != expected_changelog: - errors.append(f"{path}: {plugin_id} history 文案必须为 {expected_changelog}") + elif not isinstance(history[expected_history_key], str) or not history[expected_history_key].strip(): + errors.append(f"{path}: {plugin_id} history 当前版本说明不能为空") legacy_path = path.with_name("package.v2.json") legacy_metadata = _load_package(legacy_path).get(plugin_id) @@ -96,19 +86,6 @@ def _check_v3_metadata(path: Path, plugin_id: str, metadata: dict) -> list[str]: if legacy_metadata.get("v3") is not False: errors.append(f"{legacy_path}: {plugin_id} 必须声明 v3=false") - old_version = str(legacy_metadata.get("version") or "").strip() - old_parts = _version_parts(old_version) - normalized_old = _normalized_version(old_version) - normalized_new = _normalized_version(version) - if old_parts is None or normalized_old is None or normalized_new is None or version_parts is None: - errors.append(f"{path}: {plugin_id} 无法比较版本 {old_version} -> {version}") - return errors - if version_parts[0] != old_parts[0]: - errors.append(f"{path}: {plugin_id} V3 不得提升主版本:{old_version} -> {version}") - if version_parts[1] <= (old_parts[1] if len(old_parts) > 1 else 0): - errors.append(f"{path}: {plugin_id} V3 必须提升小版本:{old_version} -> {version}") - if normalized_new <= normalized_old: - errors.append(f"{path}: {plugin_id} V3 版本必须高于旧版本:{old_version} -> {version}") return errors diff --git a/package.v3.json b/package.v3.json index 1e517bc7..5c7129ea 100644 --- a/package.v3.json +++ b/package.v3.json @@ -3,13 +3,13 @@ "name": "订阅助手(增强版)", "description": "多场景管理订阅,实现订阅全生命周期管理。", "labels": "订阅", - "version": "0.7", + "version": "0.7.1", "icon": "https://raw.githubusercontent.com/InfinityPacer/MoviePilot-Plugins/main/icons/subscribeassistantenhanced.png", "author": "InfinityPacer", "level": 1, "system_version": ">=3.0.0", "history": { - "v0.7": "MoviePilot V3 版本订阅助手(增强版)插件" + "v0.7.1": "适配 MoviePilot V3 统一响应封装,避免打开配置页时提示未知错误" }, "release": true }, diff --git a/plugins.v3/subscribeassistantenhanced/README.md b/plugins.v3/subscribeassistantenhanced/README.md index 05365dec..d188fb86 100644 --- a/plugins.v3/subscribeassistantenhanced/README.md +++ b/plugins.v3/subscribeassistantenhanced/README.md @@ -16,6 +16,7 @@ ## 版本更新日志 +- v0.7.1:适配 MoviePilot V3 统一响应封装,避免打开配置页时提示未知错误。 - v0.7:MoviePilot V3 版本订阅助手(增强版)插件 - v0.6.12:配置页滚动条跟随主程序样式,并恢复移动端修改项计数。 - v0.6.11:修正全集洗版可能阻断新增集自动纠错的问题。 diff --git a/plugins.v3/subscribeassistantenhanced/__init__.py b/plugins.v3/subscribeassistantenhanced/__init__.py index e6c40e61..9ea45f15 100644 --- a/plugins.v3/subscribeassistantenhanced/__init__.py +++ b/plugins.v3/subscribeassistantenhanced/__init__.py @@ -15,7 +15,6 @@ from apscheduler.triggers.cron import CronTrigger from app.plugins import _PluginBase -from app import schemas from app.log import logger from app.core.event import eventmanager from app.core.metainfo import MetaInfo @@ -91,7 +90,7 @@ class SubscribeAssistantEnhanced(_PluginBase): # 插件图标 plugin_icon = "https://raw.githubusercontent.com/InfinityPacer/MoviePilot-Plugins/main/icons/subscribeassistantenhanced.png" # 插件版本 - plugin_version = "0.7" + plugin_version = "0.7.1" _site_cache_candidate_helper_warned = False # 插件作者 plugin_author = "InfinityPacer" @@ -1344,7 +1343,6 @@ def get_api(self) -> List[Dict[str, Any]]: "auth": "bear", "summary": "订阅助手(增强版)概览", "description": "返回各业务域启用状态与待定/监控计数", - "response_model": schemas.Response[dict], }] def _api_summary(self) -> Dict[str, Any]: diff --git a/tests/ci/test_plugin_release_gate.py b/tests/ci/test_plugin_release_gate.py index f4cf7912..d764029f 100644 --- a/tests/ci/test_plugin_release_gate.py +++ b/tests/ci/test_plugin_release_gate.py @@ -46,7 +46,11 @@ def _write_fixture( "name": "示例", "version": package_version, "system_version": ">=3.0.0", - "history": history or {f"v{package_version}": "MoviePilot V3 版本示例插件"}, + "history": ( + history + if history is not None + else {f"v{package_version}": "适配 MoviePilot V3 发布契约"} + ), "release": True, } } @@ -232,14 +236,13 @@ def test_default_index_plugins_all_have_a_v2_compatibility_path() -> None: assert missing == [] -def test_checker_rejects_major_version_increase(tmp_path: Path) -> None: - """V3 迁移绝不能提升主版本。""" +def test_checker_does_not_reapply_migration_version_rules(tmp_path: Path) -> None: + """V3 独立发布后不再用旧代版本约束后续版本演进。""" _write_fixture(tmp_path, old_version="1.2.3", package_version="2.0", source_version="2.0") result = _run_checker(tmp_path) - assert result.returncode == 1 - assert "不得提升主版本" in result.stdout + assert result.returncode == 0, result.stdout + result.stderr def test_checker_requires_legacy_v3_block(tmp_path: Path) -> None: @@ -253,10 +256,10 @@ def test_checker_requires_legacy_v3_block(tmp_path: Path) -> None: def test_checker_requires_single_current_history_entry(tmp_path: Path) -> None: - """V3 history 只允许当前版本一条标准迁移说明。""" + """V3 history 只允许当前版本一条发布说明。""" _write_fixture( tmp_path, - history={"v1.3": "MoviePilot V3 版本示例插件", "v1.2.3": "旧记录"}, + history={"v1.3": "适配 MoviePilot V3 发布契约", "v1.2.3": "旧记录"}, ) result = _run_checker(tmp_path) @@ -265,6 +268,28 @@ def test_checker_requires_single_current_history_entry(tmp_path: Path) -> None: assert "history 必须只保留当前版本" in result.stdout +def test_checker_accepts_release_specific_history(tmp_path: Path) -> None: + """V3 后续版本应使用描述实际行为变化的发布说明。""" + _write_fixture( + tmp_path, + history={"v1.3": "适配 MoviePilot V3 统一响应封装,避免打开配置页时提示未知错误"}, + ) + + result = _run_checker(tmp_path) + + assert result.returncode == 0, result.stdout + result.stderr + + +def test_checker_rejects_blank_current_history(tmp_path: Path) -> None: + """当前版本仍必须提供可用于 GitHub Release 的非空说明。""" + _write_fixture(tmp_path, history={"v1.3": " "}) + + result = _run_checker(tmp_path) + + assert result.returncode == 1 + assert "history 当前版本说明不能为空" in result.stdout + + def test_pre_push_propagates_version_gate_failure(tmp_path: Path) -> None: """pre-push 必须传播 checker 非零状态。""" _write_fixture(tmp_path, package_version="1.3", source_version="1.4") diff --git a/tests/v3/subscribeassistantenhanced/test_dashboard.py b/tests/v3/subscribeassistantenhanced/test_dashboard.py index 31a47d7c..46920cd6 100644 --- a/tests/v3/subscribeassistantenhanced/test_dashboard.py +++ b/tests/v3/subscribeassistantenhanced/test_dashboard.py @@ -1,5 +1,8 @@ """前端入口 smoke:只读概览 API 保留;详情页与仪表盘已下线。""" -from app import schemas +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from app.api.response import ResponseAPIRoute from subscribeassistantenhanced import SubscribeAssistantEnhanced @@ -14,7 +17,24 @@ def _plugin(self): def test_get_api_exposes_summary(self): apis = self._plugin().get_api() route = next(a for a in apis if a["path"] == "/summary") - assert route["response_model"] == schemas.Response[dict] + assert "response_model" not in route + + def test_summary_uses_main_program_response_wrapper(self): + plugin = self._plugin() + route = next(a for a in plugin.get_api() if a["path"] == "/summary").copy() + route.pop("auth") + app = FastAPI() + app.router.route_class = ResponseAPIRoute + app.add_api_route(**route) + + response = TestClient(app).get("/summary") + + assert response.status_code == 200 + assert response.json() == { + "success": True, + "message": "", + "data": plugin._api_summary(), + } def test_api_summary_shape(self): summary = self._plugin()._api_summary()