import importlib.util import json import tempfile import unittest import sys from pathlib import Path from unittest import mock MODULE_PATH = Path(__file__).resolve().parents[1] / "release_mac_alpha.py" sys.path.insert(0, str(MODULE_PATH.parent)) SPEC = importlib.util.spec_from_file_location("release_mac_alpha", MODULE_PATH) release = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(release) class ReleaseHarnessTests(unittest.TestCase): def test_parse_revision_accepts_current_alpha_contract(self): self.assertEqual(release.parse_revision_from_version("0.1.0-alpha.12"), 12) with self.assertRaises(release.ReleaseError): release.parse_revision_from_version("0.1.0") def test_replace_exact_regex_fails_closed_on_ambiguous_input(self): self.assertEqual( release.replace_exact_regex( "revision=2", r"revision=\d+", "revision=3", label="revision", ), "revision=3", ) with self.assertRaises(release.ReleaseError): release.replace_exact_regex( "revision=2\nrevision=2", r"revision=\d+", "revision=3", label="revision", ) def test_set_release_version_updates_source_and_independent_contract_test(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) source = root / "ProjectQuintReleaseVersion.cs" tests = root / "ProjectQuintReleaseVersionTests.cs" source.write_text( "public const int ChannelRevision = 2;\n" "public const int BuildNumber = 2;\n", encoding="utf-8", ) tests.write_text( "Assert.That(ProjectQuintReleaseVersion.ChannelRevision, Is.EqualTo(2));\n" "Assert.That(ProjectQuintReleaseVersion.BuildNumber, Is.EqualTo(2));\n" 'Assert.That(ProjectQuintReleaseVersion.DisplayVersion, Is.EqualTo("0.1.0-alpha.2"));\n' 'Is.EqualTo("0.1.0-alpha.2 (build 2)"));\n', encoding="utf-8", ) with mock.patch.object(release, "VERSION_SOURCE", source), mock.patch.object( release, "VERSION_TEST", tests ): release.set_release_version(3) self.assertIn("ChannelRevision = 3", source.read_text()) self.assertIn("BuildNumber = 3", source.read_text()) self.assertIn("0.1.0-alpha.3", tests.read_text()) self.assertIn("build 3", tests.read_text()) def test_update_site_writes_download_manifest_and_one_changelog_entry(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) download = root / "download.astro" changelog = root / "changelog.astro" manifest = root / "latest.json" appcast = root / "appcast.xml" readme = root / "README.md" download.write_text( 'href="/downloads/0.1.0-alpha.2/ProjectQuint-0.1.0-alpha.2-macOS-universal.dmg"\n' "0.1.0 ALPHA 2\n" "
VERSION
0.1.0-alpha.2
\n" "
SIZE
77.7 MB
\n" f"{'a' * 64}\n", encoding="utf-8", ) changelog.write_text('
\n', encoding="utf-8") manifest.write_text("{}\n", encoding="utf-8") appcast.write_text("", encoding="utf-8") readme.write_text("# Site\n", encoding="utf-8") digest = "b" * 64 signature = "A" * 86 + "==" patches = [ mock.patch.object(release, "DOWNLOAD_PAGE", download), mock.patch.object(release, "CHANGELOG_PAGE", changelog), mock.patch.object(release, "UPDATE_MANIFEST", manifest), mock.patch.object(release, "SPARKLE_APPCAST", appcast), mock.patch.object(release, "SITE_README", readme), ] for patch in patches: patch.start() try: release.update_site( 3, digest, 80_000_000, signature, "Release title", ["Change one", "Change two"], "2026-07-29T18:00:00+09:00", ) release.update_site( 3, digest, 80_000_000, signature, "Release title", ["Change one", "Change two"], "2026-07-29T18:00:00+09:00", ) finally: for patch in reversed(patches): patch.stop() self.assertIn("0.1.0-alpha.3", download.read_text()) self.assertIn("80.0 MB", download.read_text()) self.assertEqual(changelog.read_text().count("0.1.0-ALPHA.3"), 1) self.assertEqual(readme.read_text().count("## 0.1.0-alpha.3 release"), 1) data = json.loads(manifest.read_text()) self.assertEqual(data["buildNumber"], 3) self.assertEqual(data["sha256"], digest) self.assertEqual(data["sizeBytes"], 80_000_000) appcast_text = appcast.read_text() self.assertIn("0.1.0-alpha.3", appcast_text) self.assertIn('sparkle:version>3