Files
MoviePilot/tests/test_release_notes.py

112 lines
5.7 KiB
Python

"""发布记录的范围、作者、分页与手工摘要保护契约。"""
import json
import pytest
from scripts import release_notes
def make_commit(sha: str, subject: str, login: str | None = "contributor", parents: int = 1) -> dict:
"""构造包含 GitHub 作者及原始 Git 显示名的提交响应。"""
return {
"sha": sha,
"html_url": f"https://github.com/owner/repo/commit/{sha}",
"parents": [{}] * parents,
"author": {"login": login} if login else None,
"commit": {"message": subject, "author": {"name": "Display Name"}},
}
def test_changelog_preserves_subjects_and_distinct_commits() -> None:
"""不分类、翻译或按标题去重,真实账号与未关联作者分别展示。"""
commits = [
make_commit("a" * 40, "fix(scope): 原始标题\n详细正文"),
make_commit("b" * 40, "fix(scope): 原始标题", login=None),
make_commit("c" * 40, "Merge pull request #1", parents=2),
make_commit("d" * 40, "合并提交", parents=2),
make_commit("e" * 40, "build(plugin-market): sync default from MoviePilot-Wiki@123"),
make_commit("f" * 40, "ci: improve pipeline"),
]
body = release_notes.changelog(commits + commits[:1])
assert body.count("fix(scope): 原始标题") == 2
assert "by @contributor" in body
assert "by Display Name" in body
assert "@Display Name" not in body
assert "Merge" not in body and "合并提交" not in body
assert "plugin-market" not in body
assert "ci: improve pipeline" in body
assert "详细正文" not in body
def test_previous_tag_excludes_current_future_and_other_major(monkeypatch) -> None:
"""同版本重打包仍从上一正式版计算,不受较高版本或预发布标签影响。"""
monkeypatch.setattr(release_notes, "command", lambda *_: "v2.9.9\nv3.1.2\nv3.1.2-1\nv3.1.3\nv3.1.4\nv3.1.3-beta.1\n")
assert release_notes.previous_tag("v3.1.3") == "v3.1.2-1"
assert release_notes.previous_tag("v3.1.2-2") == "v3.1.2-1"
assert release_notes.previous_tag("v3.0.0") is None
def test_collect_commits_includes_every_page(monkeypatch) -> None:
"""跨页发布记录包含直接提交,比较终点使用固定 SHA 而非可移动标签。"""
calls = []
def fake_command(*args):
"""模拟 GitHub Compare API 的多页响应。"""
calls.append(args)
return json.dumps([{"commits": [make_commit("a", "first")]}, {"commits": [make_commit("b", "second")]}])
monkeypatch.setattr(release_notes, "command", fake_command)
commits = release_notes.collect_commits("owner/repo", "fixed-sha", "v3.1.2")
assert [commit["sha"] for commit in commits] == ["a", "b"]
assert calls == [("gh", "api", "--paginate", "--slurp", "repos/owner/repo/compare/v3.1.2...fixed-sha?per_page=100")]
def test_first_release_collects_entire_history(monkeypatch) -> None:
"""首次发布不凭空选择其它大版本,提交历史按时间正序输出。"""
monkeypatch.setattr(release_notes, "command", lambda *_: json.dumps([[make_commit("b", "new")], [make_commit("a", "old")]]))
commits = release_notes.collect_commits("owner/repo", "fixed-sha", None)
assert [commit["sha"] for commit in commits] == ["a", "b"]
@pytest.mark.parametrize("summary", ["", "- 手工功能说明\n\n### 注意事项\n\n原样保留 `代码` 与 @author", "说明\n\n## 手工子标题\n\n细节"])
def test_rebuild_preserves_manual_summary_and_refreshes_records(summary: str) -> None:
"""重建保留手工摘要,刷新自动章节,正文始终仅包含三个约定章节。"""
old = release_notes.release_body(summary, "old records", "owner/repo", "v3.1.1", "v3.1.2")
preserved = release_notes.manual_summary(old)
new = release_notes.release_body(preserved, "new records", "owner/repo", "v3.1.1", "v3.1.2")
assert preserved == summary
assert "old records" not in new
assert "new records" in new
assert "https://github.com/owner/repo/compare/v3.1.1...v3.1.2" in new
assert "贡献者" not in new
def test_legacy_manual_body_is_preserved_but_generated_body_is_not_summary() -> None:
"""兼容旧手工公告,避免把原有自动分类记录当作本次用户摘要。"""
assert release_notes.manual_summary("手工公告\n- 新功能") == "手工公告\n- 新功能"
assert release_notes.manual_summary("### ✨ 新功能\n\n- generated by @author") == ""
assert release_notes.manual_summary("手工公告\n\n### ✨ 新功能\n- generated") == "手工公告"
@pytest.mark.parametrize("frontend", [False, True])
def test_main_emits_workflow_body_without_evaluating_commit_text(tmp_path, monkeypatch, frontend) -> None:
"""前端仅输出列表,后端保留手工内容;提交中的工作流分隔符不得截断正文。"""
environment = tmp_path / "github.env"
monkeypatch.setenv("GITHUB_ENV", str(environment))
monkeypatch.setenv("RELEASE_BODY", "## 本次更新内容\n\n手工公告\n\n## 完整变更记录\nold")
monkeypatch.setattr(release_notes, "previous_tag", lambda _: "v3.1.2")
monkeypatch.setattr(release_notes, "collect_commits", lambda *_: [make_commit("a" * 40, "fix: $() EOF")])
args = ["release_notes.py", "--repository", "owner/repo", "--tag", "v3.1.3", "--source", "fixed-sha"]
if frontend:
args.append("--changelog-only")
monkeypatch.setattr("sys.argv", args)
release_notes.main()
content = environment.read_text(encoding="utf-8")
delimiter = content.splitlines()[0].split("<<")[1]
assert content.splitlines()[-1] == delimiter
assert "fix: $() EOF by @contributor" in content
assert ("## 本次更新内容" in content) is not frontend
assert ("手工公告" in content) is not frontend
assert ("## 版本对比" in content) is not frontend