mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-11-06 16:07:55 +00:00
- Implemented MSI packaging for stable releases, including installation scripts and metadata generation. - Added tests to ensure MSI files are correctly generated and verified against expected properties. - Updated release workflows to include MSI installation and signing steps. - Enhanced artifact management to conditionally include MSI files based on release type.
239 lines
10 KiB
Python
239 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
RELEASE_SCRIPTS = Path(__file__).resolve().parents[1] / "release"
|
|
sys.path.insert(0, str(RELEASE_SCRIPTS))
|
|
|
|
import generate_release_metadata # noqa: E402
|
|
import verify_release_metadata # noqa: E402
|
|
|
|
|
|
class GenerateReleaseMetadataTests(unittest.TestCase):
|
|
def make_release(self, directory: Path, version: str = "2.0.0") -> None:
|
|
for name in generate_release_metadata.expected_artifacts(version):
|
|
(directory / name).write_bytes(f"payload:{name}".encode())
|
|
updater_names = {
|
|
template.format(version=version)
|
|
for template in generate_release_metadata.updater_artifacts(version).values()
|
|
}
|
|
for name in updater_names:
|
|
(directory / f"{name}.sig").write_text(
|
|
f"signature-for-{name}\n", encoding="utf-8"
|
|
)
|
|
|
|
def test_generates_download_and_legacy_updater_manifests(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
directory = Path(temporary)
|
|
self.make_release(directory)
|
|
downloads, updater = generate_release_metadata.generate(
|
|
directory,
|
|
version="2.0.0",
|
|
tag="v2.0.0",
|
|
base_url="https://downloads.nyaterm.app/",
|
|
notes="release notes",
|
|
pub_date="2026-08-31T00:00:00Z",
|
|
)
|
|
|
|
self.assertEqual(len(downloads["platforms"]), 10)
|
|
self.assertEqual(len(updater["platforms"]), 12)
|
|
for architecture in ("x86_64", "aarch64"):
|
|
key = f"windows-{architecture}-msi"
|
|
self.assertTrue(downloads["platforms"][key]["url"].endswith(".msi"))
|
|
self.assertEqual(downloads["platforms"][key]["url"], updater["platforms"][key]["url"])
|
|
self.assertTrue(updater["platforms"][key]["signature"].startswith("signature-for-"))
|
|
self.assertIn("windows-x86_64-portable", updater["platforms"])
|
|
self.assertEqual(
|
|
downloads["platforms"]["darwin-aarch64"]["url"],
|
|
"https://downloads.nyaterm.app/releases/v2.0.0/"
|
|
"NyaTerm_2.0.0_macos_arm64.dmg",
|
|
)
|
|
self.assertEqual(
|
|
updater["platforms"]["windows-x86_64"],
|
|
updater["platforms"]["windows-x86_64-nsis"],
|
|
)
|
|
self.assertEqual(
|
|
json.loads((directory / "downloads.json").read_text()), downloads
|
|
)
|
|
|
|
checksum_lines = (directory / "SHA256SUMS").read_text().splitlines()
|
|
self.assertEqual(checksum_lines, sorted(checksum_lines, key=lambda line: line[66:]))
|
|
portable = directory / "NyaTerm_2.0.0_windows_x64_portable.zip"
|
|
self.assertIn(
|
|
f"{hashlib.sha256(portable.read_bytes()).hexdigest()} {portable.name}",
|
|
checksum_lines,
|
|
)
|
|
|
|
def test_stable_and_prerelease_publish_to_separate_channel_manifests(self) -> None:
|
|
self.assertEqual(
|
|
generate_release_metadata.channel_manifest_destinations("2.0.0"),
|
|
{
|
|
"latest.json": "latest.json",
|
|
"downloads.json": "downloads.json",
|
|
},
|
|
)
|
|
preview = generate_release_metadata.channel_manifest_destinations(
|
|
"2.0.0-preview.2"
|
|
)
|
|
self.assertEqual(
|
|
preview,
|
|
{
|
|
"latest.json": "channels/preview/latest.json",
|
|
"downloads.json": "channels/preview/downloads.json",
|
|
},
|
|
)
|
|
self.assertNotIn("latest.json", preview.values())
|
|
self.assertNotIn("downloads.json", preview.values())
|
|
|
|
def test_rejects_mismatched_tag_and_missing_signature(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
directory = Path(temporary)
|
|
self.make_release(directory)
|
|
with self.assertRaises(ValueError):
|
|
generate_release_metadata.generate(
|
|
directory,
|
|
version="2.0.0",
|
|
tag="v2.0.1",
|
|
base_url="https://downloads.nyaterm.app",
|
|
notes="",
|
|
pub_date="2026-08-31T00:00:00Z",
|
|
)
|
|
next(directory.glob("*.sig")).unlink()
|
|
with self.assertRaisesRegex(RuntimeError, "missing updater signature"):
|
|
generate_release_metadata.generate(
|
|
directory,
|
|
version="2.0.0",
|
|
tag="v2.0.0",
|
|
base_url="https://downloads.nyaterm.app",
|
|
notes="",
|
|
pub_date="2026-08-31T00:00:00Z",
|
|
)
|
|
|
|
def test_rejects_empty_signature_and_keeps_prerelease_urls_versioned(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
directory = Path(temporary)
|
|
version = "2.1.0-beta.1"
|
|
self.make_release(directory, version)
|
|
signature = next(directory.glob("*.sig"))
|
|
signature.write_text("\n", encoding="utf-8")
|
|
with self.assertRaisesRegex(RuntimeError, "empty updater signature"):
|
|
generate_release_metadata.generate(
|
|
directory,
|
|
version=version,
|
|
tag=f"v{version}",
|
|
base_url="https://downloads.nyaterm.app",
|
|
notes="",
|
|
pub_date="2026-08-31T00:00:00Z",
|
|
)
|
|
|
|
signature.write_text("verified-signature\n", encoding="utf-8")
|
|
downloads, updater = generate_release_metadata.generate(
|
|
directory,
|
|
version=version,
|
|
tag=f"v{version}",
|
|
base_url="https://downloads.nyaterm.app",
|
|
notes="prerelease",
|
|
pub_date="2026-08-31T00:00:00Z",
|
|
)
|
|
for manifest in (downloads, updater):
|
|
self.assertFalse(any(key.endswith("-msi") for key in manifest["platforms"]))
|
|
for platform in manifest["platforms"].values():
|
|
self.assertIn(f"/releases/v{version}/", platform["url"])
|
|
self.assertEqual(len(downloads["platforms"]), 8)
|
|
self.assertEqual(len(updater["platforms"]), 10)
|
|
|
|
def test_stable_requires_both_msi_files_and_their_signatures(self) -> None:
|
|
for suffix, message in ((".msi", "missing release artifacts"),
|
|
(".msi.sig", "missing updater signature")):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
directory = Path(temporary)
|
|
self.make_release(directory)
|
|
(directory / f"NyaTerm_2.0.0_windows_arm64{suffix}").unlink()
|
|
with self.subTest(suffix=suffix), self.assertRaisesRegex(RuntimeError, message):
|
|
generate_release_metadata.generate(
|
|
directory, version="2.0.0", tag="v2.0.0",
|
|
base_url="https://downloads.nyaterm.app", notes="", pub_date="2026-10-26T00:00:00Z",
|
|
)
|
|
|
|
def test_build_metadata_hyphens_do_not_select_preview(self) -> None:
|
|
version = "2.0.0+build-with-hyphen"
|
|
self.assertEqual(len(generate_release_metadata.download_artifacts(version)), 10)
|
|
self.assertEqual(len(generate_release_metadata.updater_artifacts(version)), 12)
|
|
self.assertEqual(generate_release_metadata.channel_manifest_destinations(version)["latest.json"], "latest.json")
|
|
|
|
def test_rejects_noncanonical_release_base_urls(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
directory = Path(temporary)
|
|
self.make_release(directory)
|
|
for base_url in (
|
|
"http://downloads.nyaterm.app",
|
|
"https://download.nyaterm.app",
|
|
"https://downloads.nyaterm.app/releases",
|
|
"https://downloads.nyaterm.app?source=release",
|
|
):
|
|
with self.subTest(base_url=base_url), self.assertRaisesRegex(
|
|
ValueError, "canonical origin"
|
|
):
|
|
generate_release_metadata.generate(
|
|
directory,
|
|
version="2.0.0",
|
|
tag="v2.0.0",
|
|
base_url=base_url,
|
|
notes="",
|
|
pub_date="2026-09-22T00:00:00Z",
|
|
)
|
|
|
|
def test_verifier_requires_exact_urls_complete_platforms_and_integrity(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
directory = Path(temporary)
|
|
self.make_release(directory)
|
|
generate_release_metadata.generate(
|
|
directory,
|
|
version="2.0.0",
|
|
tag="v2.0.0",
|
|
base_url="https://downloads.nyaterm.app",
|
|
notes="",
|
|
pub_date="2026-09-22T00:00:00Z",
|
|
)
|
|
urls = verify_release_metadata.verify(
|
|
directory,
|
|
version="2.0.0",
|
|
tag="v2.0.0",
|
|
base_url="https://downloads.nyaterm.app",
|
|
check_assets=False,
|
|
)
|
|
expected_urls = {
|
|
template.format(version="2.0.0")
|
|
for template in (
|
|
list(generate_release_metadata.download_artifacts("2.0.0").values())
|
|
+ list(generate_release_metadata.updater_artifacts("2.0.0").values())
|
|
)
|
|
}
|
|
self.assertEqual(len(urls), len(expected_urls))
|
|
|
|
latest_path = directory / "latest.json"
|
|
latest = json.loads(latest_path.read_text(encoding="utf-8"))
|
|
latest["platforms"]["windows-x86_64"]["url"] = (
|
|
"https://downloads.nyaterm.app/releases/releases/v2.0.0/"
|
|
"NyaTerm_2.0.0_windows_x64-setup.exe"
|
|
)
|
|
latest_path.write_text(json.dumps(latest), encoding="utf-8")
|
|
with self.assertRaisesRegex(ValueError, "noncanonical"):
|
|
verify_release_metadata.verify(
|
|
directory,
|
|
version="2.0.0",
|
|
tag="v2.0.0",
|
|
base_url="https://downloads.nyaterm.app",
|
|
check_assets=False,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|