Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 3 additions & 26 deletions .github/scripts/check_plugin_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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


Expand Down
4 changes: 2 additions & 2 deletions package.v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
1 change: 1 addition & 0 deletions plugins.v3/subscribeassistantenhanced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

## 版本更新日志

- v0.7.1:适配 MoviePilot V3 统一响应封装,避免打开配置页时提示未知错误。
- v0.7:MoviePilot V3 版本订阅助手(增强版)插件
- v0.6.12:配置页滚动条跟随主程序样式,并恢复移动端修改项计数。
- v0.6.11:修正全集洗版可能阻断新增集自动纠错的问题。
Expand Down
4 changes: 1 addition & 3 deletions plugins.v3/subscribeassistantenhanced/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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]:
Expand Down
39 changes: 32 additions & 7 deletions tests/ci/test_plugin_release_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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")
Expand Down
24 changes: 22 additions & 2 deletions tests/v3/subscribeassistantenhanced/test_dashboard.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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()
Expand Down