Files
crossdesk/.github/scripts/test_update_version_manifest.py
T

155 lines
8.3 KiB
Python

"""Regression coverage for independent and combined release metadata."""
import copy
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
import update_version_manifest as manifest
class ManifestTest(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory(prefix="crossdesk-manifest-")
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
self.artifacts = self.root / "release"
self.artifacts.mkdir()
self.current = {
"version": "1.6.0-20260930", "latest_version": "1.6.0-20260930",
"releaseDate": "2026-09-30", "releaseNotes": "Desktop notes", "patch": 0,
"downloads": {"windows-x64": {"url": "https://downloads.crossdesk.cn/existing.exe"}},
"custom": {"keep": True},
}
def files(self, *names):
for path in self.artifacts.iterdir():
path.unlink()
for name in names:
(self.artifacts / name).write_bytes(b"release fixture")
return manifest.artifact_downloads(self.artifacts)
def merge(self, tag, downloads, current=None):
return manifest.merge_release(current or self.current, tag, tag, "Release notes", downloads)
def test_mobile_preserves_desktop_metadata_and_downloads(self):
downloads = self.files("crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk")
original = copy.deepcopy(self.current)
result = self.merge("android-v1.6.0-20261002", downloads)
for key, value in original.items():
if key != "downloads":
self.assertEqual(result[key], value)
self.assertEqual(result["downloads"]["windows-x64"], original["downloads"]["windows-x64"])
self.assertEqual(result["downloads"]["android-arm64"]["url"],
"https://downloads.crossdesk.cn/crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk")
self.assertEqual(result["downloads"]["android-arm64"]["version"], "1.6.0-20261002")
self.assertEqual(result["downloads"]["android-arm64"]["releaseDate"], "2026-10-02")
self.assertNotIn("platforms", result)
self.assertEqual(self.current, original)
def test_sequential_platform_releases_retain_each_other(self):
for order in (("ios", "android"), ("android", "ios")):
result = copy.deepcopy(self.current)
for platform in order:
version, extension = ("0.0.2", "zip") if platform == "ios" else ("1.6.0", "apk")
downloads = self.files(f"crossdesk-{platform}-arm64-unsigned-v{version}-20261002.{extension}")
result = self.merge(f"{platform}-v{version}-20261002", downloads, result)
self.assertNotIn("platforms", result)
self.assertEqual(result["version"], self.current["version"])
self.assertTrue({"windows-x64", "ios-arm64", "android-arm64"} <= result["downloads"].keys())
self.assertEqual(result["downloads"]["ios-arm64"]["version"], "0.0.2-20261002")
self.assertEqual(result["downloads"]["android-arm64"]["version"], "1.6.0-20261002")
def test_combined_release_records_independent_mobile_versions(self):
downloads = self.files("crossdesk-win-x64-v1.6.1-20261002.exe",
"crossdesk-win-x64-portable-v1.6.1-20261002.zip",
"crossdesk-ios-arm64-unsigned-v0.0.2-20261002.zip",
"crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk")
result = self.merge("v1.6.1-20261002", downloads)
self.assertEqual(result["version"], "1.6.1-20261002")
self.assertEqual(result["downloads"]["ios-arm64"]["version"], "0.0.2-20261002")
self.assertEqual(result["downloads"]["android-arm64"]["version"], "1.6.0-20261002")
self.assertNotIn("platforms", result)
def test_obsolete_platforms_are_removed_on_any_release(self):
for tag, artifact in (
("android-v1.6.0-20261002", "crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk"),
("v1.6.1-20261002", "crossdesk-win-x64-v1.6.1-20261002.exe"),
):
downloads = self.files(artifact)
expected = self.merge(tag, downloads)
for obsolete in ({"ios": {"version": "0.0.1"}}, []):
current = {**copy.deepcopy(self.current), "platforms": obsolete}
original = copy.deepcopy(current)
self.assertEqual(self.merge(tag, downloads, current), expected)
self.assertEqual(current, original)
def test_mobile_tag_must_match_artifact_and_platform(self):
downloads = self.files("crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk")
for tag in ("android-v1.7.0-20261002", "ios-v1.6.0-20261002"):
with self.assertRaises(ValueError):
self.merge(tag, downloads)
def test_signed_android_release_updates_existing_unsigned_download(self):
unsigned = self.files("crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk")
current = self.merge("android-v1.6.0-20261002", unsigned)
signed = self.files("crossdesk-android-arm64-v1.6.0-20261002.apk")
result = self.merge("android-v1.6.0-20261002", signed, current)
self.assertEqual(result["downloads"]["android-arm64"]["url"],
"https://downloads.crossdesk.cn/crossdesk-android-arm64-v1.6.0-20261002.apk")
self.assertEqual(result["version"], self.current["version"])
self.assertEqual(result["downloads"]["windows-x64"], self.current["downloads"]["windows-x64"])
self.assertNotIn("platforms", result)
def test_signed_android_is_preferred_when_release_retains_unsigned_apk(self):
downloads = self.files("crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk",
"crossdesk-android-arm64-v1.6.0-20261002.apk")
self.assertEqual(downloads["android-arm64"]["filename"],
"crossdesk-android-arm64-v1.6.0-20261002.apk")
with self.assertRaises(ValueError):
self.files("crossdesk-android-arm64-unsigned-v1.6.0-20261001.apk",
"crossdesk-android-arm64-v1.6.0-20261002.apk")
def test_invalid_current_manifest_is_rejected(self):
downloads = self.files("crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk")
for current in ([], {}, {"downloads": []}):
with self.assertRaises(ValueError):
manifest.merge_release(current, "android-v1.6.0-20261002", "Android", "", downloads)
def test_missing_duplicate_and_invalid_artifacts_are_rejected(self):
for names in ((), ("crossdesk-android-arm64-unsigned-v1.6.0-20260230.apk",),
("crossdesk-android-arm64-unsigned-v1.6.0-20261001.apk",
"crossdesk-android-arm64-unsigned-v1.6.0-20261002.apk")):
with self.assertRaises(ValueError):
self.files(*names)
def test_hotfix_version_and_date_formats(self):
for value in ("v1.6.0-2-20261002", "v1.6.0-20261002-2"):
info = manifest.version_info(value)
self.assertEqual(info["patch"], 2)
self.assertEqual(info["releaseDate"], "2026-10-02")
def test_cli_preserves_literal_notes_and_release_metadata(self):
self.files("crossdesk-win-x64-v1.6.1-20261002.exe")
notes = '中文 release notes\n"quoted" $(not-a-command) $HOME `literal`'
release = {"tagName": "v1.6.1-20261002", "name": "Desktop release", "body": notes}
(self.root / "current.json").write_text(json.dumps(self.current))
(self.root / "release.json").write_text(json.dumps(release))
output = self.root / "version.json"
subprocess.run([sys.executable, str(Path(manifest.__file__)),
"--current", str(self.root / "current.json"), "--output", str(output),
"--artifacts", str(self.artifacts), "--release-json", str(self.root / "release.json")],
check=True, capture_output=True, text=True)
result = json.loads(output.read_text())
self.assertEqual(result["releaseNotes"], notes)
self.assertEqual(result["releaseName"], "Desktop release")
self.assertEqual(result["tagName"], "v1.6.1-20261002")
self.assertNotIn("platforms", result)
if __name__ == "__main__":
unittest.main()