Files
XStats/scripts/release_provenance_test.py

81 lines
3.6 KiB
Python

#!/usr/bin/env python3
# Copyright (C) 2026 ysicing
# SPDX-License-Identifier: AGPL-3.0-or-later
import tempfile
import unittest
from pathlib import Path
from subprocess import run
import release_provenance
class ReleaseProvenanceTest(unittest.TestCase):
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
self.repo = Path(self.temporary.name)
self.write("project.yml", 'MARKETING_VERSION: "1.0.0"\nCURRENT_PROJECT_VERSION: "109"\n')
self.write("CHANGELOG.md", "# 更新日志\n\n## 1.0.0 · 2026-09-22\n\n- 首次发布。\n")
self.write("README.md", "# XStats\n")
self.write("Sources/App.swift", "let version = 1\n")
self.write(".gitignore", "dist/\n")
self.git("init", "-b", "main")
self.git("config", "user.name", "XStats Test")
self.git("config", "user.email", "test@example.test")
self.git("add", ".")
self.git("commit", "-m", "initial")
def tearDown(self) -> None:
self.temporary.cleanup()
def write(self, relative: str, content: str) -> None:
path = self.repo / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
def git(self, *arguments: str) -> str:
result = run(["git", *arguments], cwd=self.repo, check=True, capture_output=True, text=True)
return result.stdout.strip()
def test_prepare_allows_release_metadata_but_rejects_dirty_source(self) -> None:
self.write("CHANGELOG.md", "# 更新日志\n\n## 1.0.0 · 2026-09-22\n\n- 更新说明。\n")
self.assertEqual(release_provenance.prepare(self.repo), self.git("rev-parse", "HEAD"))
self.write("Sources/App.swift", "let version = 2\n")
with self.assertRaises(release_provenance.ProvenanceError):
release_provenance.prepare(self.repo)
def test_verify_rejects_source_changes_after_build(self) -> None:
base = release_provenance.prepare(self.repo)
self.write("project.yml", 'MARKETING_VERSION: "1.0.0"\nCURRENT_PROJECT_VERSION: "110"\n')
provenance = self.repo / "dist/release-provenance.json"
release_provenance.record(self.repo, provenance, base, "1.0.0", "110")
self.git("add", "project.yml")
self.git("commit", "-m", "chore(release): 1.0.0")
release_provenance.verify(self.repo, provenance, "1.0.0", "110")
self.write("Sources/App.swift", "let version = 2\n")
self.git("add", "Sources/App.swift")
self.git("commit", "-m", "change source after build")
with self.assertRaises(release_provenance.ProvenanceError):
release_provenance.verify(self.repo, provenance, "1.0.0", "110")
def test_localized_notes_are_release_metadata_bound_to_build(self) -> None:
self.write("ReleaseNotes.json", '{"version":"1.0.0","translations":{"en":["first"]}}')
base = release_provenance.prepare(self.repo)
provenance = self.repo / "dist/release-provenance.json"
release_provenance.record(self.repo, provenance, base, "1.0.0", "109")
self.git("add", "ReleaseNotes.json")
self.git("commit", "-m", "release notes")
release_provenance.verify(self.repo, provenance, "1.0.0", "109")
self.write("ReleaseNotes.json", '{"version":"1.0.0","translations":{"en":["changed"]}}')
self.git("add", "ReleaseNotes.json")
self.git("commit", "-m", "edit translation after build")
with self.assertRaisesRegex(release_provenance.ProvenanceError, "ReleaseNotes.json"):
release_provenance.verify(self.repo, provenance, "1.0.0", "109")
if __name__ == "__main__":
unittest.main()