"""Offline tests for the compatibility tracker.""" import importlib.util import io import json import tempfile import unittest from pathlib import Path from unittest.mock import patch SCRIPT = Path(__file__).with_name("track_gitea_release.py") SPEC = importlib.util.spec_from_file_location("track_gitea_release", SCRIPT) TRACKER = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(TRACKER) class TrackerTests(unittest.TestCase): def setUp(self): self.directory = tempfile.TemporaryDirectory(prefix="tracker-test-", dir=TRACKER.ROOT) self.addCleanup(self.directory.cleanup) self.root = Path(self.directory.name) self.paths = ("COMPATIBILITY.md", "README.md", "AGENTS.md", "docs/README.zh-CN.md") self.originals = {} for name in self.paths: destination = self.root / name destination.parent.mkdir(parents=True, exist_ok=True) content = (TRACKER.ROOT / name).read_text(encoding="utf-8") destination.write_text(content, encoding="utf-8") self.originals[name] = content def read(self, name): return (self.root / name).read_text(encoding="utf-8") def assert_unchanged(self): for name, content in self.originals.items(): self.assertEqual(content, self.read(name), name) def test_new_release_adds_only_pending_rows(self): changed = TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") self.assertEqual({self.root / name for name in self.paths}, set(changed)) compatibility = self.read("COMPATIBILITY.md") self.assertIn("| 28.2.0 | [PENDING] Compatibility verification", compatibility) self.assertIn("| **28.2.0** | 2026-11-01 | [PENDING]", compatibility) self.assertIn("| **v28.0.0** | **28.0.0** | **28.0.0** | [PASS] Active |", compatibility) self.assertIn("Latest verified:** Release v28.0.0", compatibility) self.assertIn("Gitea-28.2.0%20%5BPENDING%5D%20%7C%2028.0.0%20tested-yellow", self.read("README.md")) self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("README.md")) self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("docs/README.zh-CN.md")) self.assertIn("Gitea release: 28.2.0 [PENDING]", self.read("AGENTS.md")) self.assertIn("Latest tested:** Gitea 28.0.0", self.read("README.md")) self.assertIn("最新测试:** Gitea 28.0.0", self.read("docs/README.zh-CN.md")) self.assertEqual([], TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")) def test_existing_version_is_idempotent(self): self.assertEqual([], TRACKER.apply_release(self.root, "28.1.0", "2026-10-06")) self.assert_unchanged() def test_partial_update_is_repaired(self): compatibility = self.root / "COMPATIBILITY.md" partial = self.read("COMPATIBILITY.md").replace("| 28.1.0 | [PENDING] Compatibility verification; no tested template release yet |\n", "") compatibility.write_text(partial, encoding="utf-8") self.assertEqual([compatibility], TRACKER.apply_release(self.root, "28.1.0", "2026-10-06")) self.assertIn("| 28.1.0 | [PENDING] Compatibility verification", self.read("COMPATIBILITY.md")) def test_older_version_does_not_rewrite_history(self): self.assertEqual([], TRACKER.apply_release(self.root, "28.0.0", "2026-09-29")) self.assert_unchanged() def test_dry_run_does_not_write(self): self.assertEqual(4, len(TRACKER.apply_release(self.root, "28.2.0", "2026-11-01", dry_run=True))) self.assert_unchanged() def test_missing_marker_fails_without_writing(self): compatibility = self.root / "COMPATIBILITY.md" compatibility.write_text(self.read("COMPATIBILITY.md").replace("", ""), encoding="utf-8") with self.assertRaises(ValueError): TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") self.assertEqual(self.originals["README.md"], self.read("README.md")) def test_unknown_marker_fails_without_writing(self): news = self.root / "docs/NEWS.md" news.write_text("Gitea 28.1.0 [PENDING] \n", encoding="utf-8") with self.assertRaisesRegex(ValueError, "Unknown TRACKER marker"): TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") self.assert_unchanged() def test_new_marked_document_is_discovered(self): news = self.root / "docs/NEWS.md" news.write_text("Gitea 28.1.0 [PENDING] \n", encoding="utf-8") changed = TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") self.assertIn(news, changed) self.assertEqual("Gitea 28.2.0 [PENDING] \n", news.read_text(encoding="utf-8")) def test_disagreeing_tested_versions_fail(self): chinese = self.root / "docs/README.zh-CN.md" chinese.write_text(self.read("docs/README.zh-CN.md").replace("**最新测试:** Gitea 28.0.0", "**最新测试:** Gitea 28.1.0"), encoding="utf-8") with self.assertRaisesRegex(ValueError, "disagree"): TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") def test_after_verification_new_version_becomes_pending(self): english = self.root / "README.md" chinese = self.root / "docs/README.zh-CN.md" agents = self.root / "AGENTS.md" english.write_text(self.read("README.md") .replace("**Latest tested:** Gitea 28.0.0", "**Latest tested:** Gitea 28.1.0") .replace("Gitea-28.1.0%20%5BPENDING%5D%20%7C%2028.0.0%20tested-yellow", "Gitea-28.1.0%20%7C%2028.1.0%20tested-blue") .replace("**Upstream Gitea 28.1.0:** [PENDING]", "**Upstream Gitea 28.1.0:** [PASS]"), encoding="utf-8") chinese.write_text(self.read("docs/README.zh-CN.md") .replace("**最新测试:** Gitea 28.0.0", "**最新测试:** Gitea 28.1.0") .replace("**上游 Gitea 28.1.0:** [PENDING]", "**上游 Gitea 28.1.0:** [PASS]"), encoding="utf-8") agents.write_text(self.read("AGENTS.md").replace("28.1.0 [PENDING]", "28.1.0 [PASS]"), encoding="utf-8") TRACKER.apply_release(self.root, "28.2.0", "2026-11-01") self.assertIn("Gitea-28.2.0%20%5BPENDING%5D%20%7C%2028.1.0%20tested-yellow", self.read("README.md")) self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("README.md")) self.assertIn("Latest tested:** Gitea 28.1.0", self.read("README.md")) self.assertIn("最新测试:** Gitea 28.1.0", self.read("docs/README.zh-CN.md")) def test_invalid_version_rejected(self): with self.assertRaises(ValueError): TRACKER.apply_release(self.root, "28.2.0-rc1", "2026-11-01") def test_release_api_uses_published_date(self): payload = {"tag_name": "v28.2.0", "published_at": "2026-11-01T08:15:00Z", "draft": False, "prerelease": False} with patch.object(TRACKER, "urlopen", return_value=io.BytesIO(json.dumps(payload).encode("utf-8"))) as mocked: self.assertEqual(("28.2.0", "2026-11-01"), TRACKER.fetch_release("28.2.0")) self.assertEqual(f"{TRACKER.RELEASES_API}/tags/v28.2.0", mocked.call_args.args[0].full_url) def test_prerelease_is_rejected(self): payload = {"tag_name": "v28.2.0", "published_at": "2026-11-01T08:15:00Z", "prerelease": True} with patch.object(TRACKER, "urlopen", return_value=io.BytesIO(json.dumps(payload).encode("utf-8"))): with self.assertRaises(ValueError): TRACKER.fetch_release(None) if __name__ == "__main__": unittest.main()