Files
saphid--frame-control/tests/test_publish_release.py
saphidandClaude Opus 5.5 f5f3a1f9ca Releases: exact draft match and a fixed release page (review fixes)
- publish-release.sh: the fallback takes only an untagged-... draft titled
  exactly "Frame Control X.Y.Z" (optionally ": subtitle"); a pre-release's
  draft ("9.8.7-rc.1") or one bound to another tag is refused.
- updater.js: ignore the page in update.json/the API entirely and always
  link to releases/tag/v<version> built from the validated version, so a
  path like tag/..\..\other/repo can't reach shell.openExternal.
- Tests for both, including backslash and %2e%2e traversal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:50:00 +11:00

111 lines
5.9 KiB
Python

"""scripts/publish-release.sh against a stand-in gh (tests/fakegh/gh): finds the draft
through the REST API even when its tag_name still says untagged-..., refuses
missing installers or digests, writes update.json's page from the tag, and
publishes with one PATCH. Nothing here talks to GitHub.
Run: python3 -m unittest discover -s tests
"""
import sandbox # noqa: F401 (first: keeps tests off real data and services)
import json
import os
import subprocess
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SCRIPT = ROOT / "scripts" / "publish-release.sh"
FAKEGH = ROOT / "tests" / "fakegh"
INSTALLERS = ["Frame-Control-mac-arm64.dmg", "Frame-Control-mac-arm64.zip", "Frame-Control-Setup-x64.exe",
"Frame-Control-win-x64.zip", "Frame-Control-linux-x86_64.AppImage",
"Frame-Control-linux-arm64.AppImage", "Frame-Control-linux-amd64.deb",
"Frame-Control-linux-arm64.deb"]
def draft(tag_name="untagged-c6ddfed7f75d67db2e99", name="Frame Control 9.8.7", rid=42, assets=None):
if assets is None:
assets = [{"id": 100 + i, "name": n, "size": 1000 + i, "digest": "sha256:" + "%064x" % i}
for i, n in enumerate(INSTALLERS)]
return {"id": rid, "tag_name": tag_name, "name": name, "draft": True, "prerelease": False,
"body": "notes", "html_url": "https://github.com/saphid/frame-control/releases/tag/" + tag_name,
"assets": assets}
class PublishRelease(unittest.TestCase):
def run_script(self, releases, *args, tags="v9.8.7"):
d = Path(tempfile.mkdtemp())
(d / "releases.json").write_text(json.dumps(releases))
env = dict(os.environ, PATH="%s:%s" % (FAKEGH, os.environ["PATH"]),
FAKEGH_RELEASES=str(d / "releases.json"), FAKEGH_TAGS=tags,
FAKEGH_LOG=str(d / "log"), FAKEGH_UPLOAD=str(d / "upload.json"))
p = subprocess.run(["sh", str(SCRIPT), *args], env=env, capture_output=True, text=True, timeout=30)
log = [json.loads(line) for line in (d / "log").read_text().splitlines()] if (d / "log").exists() else []
upload = json.loads((d / "upload.json").read_text()) if (d / "upload.json").exists() else None
return p, log, upload
def test_publishes_an_untagged_draft_by_title_with_the_tag_page(self):
old = {"id": 7, "name": "update.json", "size": 1, "digest": None}
rel = draft(assets=draft()["assets"] + [old])
p, log, upload = self.run_script([rel], "v9.8.7")
self.assertEqual(p.returncode, 0, p.stderr + p.stdout)
self.assertEqual(upload["page"], "https://github.com/saphid/frame-control/releases/tag/v9.8.7")
self.assertEqual(upload["version"], "9.8.7")
self.assertEqual(sorted(a["name"] for a in upload["assets"]), sorted(INSTALLERS))
self.assertIn(["api", "-X", "DELETE", "repos/saphid/frame-control/releases/assets/7"], log)
patch = next(c for c in log if "PATCH" in c)
self.assertEqual(patch[3], "repos/saphid/frame-control/releases/42")
for f in ("tag_name=v9.8.7", "draft=false", "prerelease=false", "make_latest=true"):
self.assertIn(f, patch)
self.assertTrue(all(c[0] == "api" for c in log)) # never `gh release ...` (GraphQL)
def test_an_untagged_draft_may_carry_a_subtitle(self):
p, log, upload = self.run_script([draft(name="Frame Control 9.8.7: faster")], "v9.8.7")
self.assertEqual(p.returncode, 0, p.stderr + p.stdout)
self.assertEqual(upload["version"], "9.8.7")
def test_prefers_the_release_whose_tag_name_matches(self):
p, log, upload = self.run_script([draft(name="Frame Control 9.8.7 old", rid=1),
draft(tag_name="v9.8.7", name="Renamed", rid=2)], "v9.8.7")
self.assertEqual(p.returncode, 0, p.stderr + p.stdout)
self.assertEqual(next(c for c in log if "PATCH" in c)[3], "repos/saphid/frame-control/releases/2")
def test_refuses_missing_or_unhashed_installers(self):
assets = draft()["assets"][1:]
assets[0] = dict(assets[0], digest=None)
p, log, upload = self.run_script([draft(assets=assets)], "v9.8.7")
self.assertNotEqual(p.returncode, 0)
self.assertIn("MISSING Frame-Control-mac-arm64.dmg", p.stdout)
self.assertIn("NO HASH Frame-Control-mac-arm64.zip", p.stdout)
self.assertIsNone(upload)
self.assertFalse(any(c[1:3] == ["-X", "PATCH"] for c in log))
def test_refuses_ambiguous_or_absent_drafts_and_missing_tags(self):
p, _, _ = self.run_script([draft(rid=1), draft(rid=2)], "v9.8.7")
self.assertNotEqual(p.returncode, 0)
self.assertIn("2 releases", p.stderr)
p, _, _ = self.run_script([draft(name="Frame Control 9.8.70")], "v9.8.7")
self.assertNotEqual(p.returncode, 0)
self.assertIn("no release", p.stderr)
# A pre-release's draft, or one bound to an unrelated tag, is never taken for v9.8.7.
for rel in (draft(name="Frame Control 9.8.7-rc.1"), draft(name="Frame Control 9.8.7.1"),
draft(tag_name="kdeconnect-frame-1"), draft(tag_name="")):
p, log, upload = self.run_script([rel], "v9.8.7")
self.assertNotEqual(p.returncode, 0, rel)
self.assertIn("no release", p.stderr)
self.assertIsNone(upload)
self.assertFalse(any("PATCH" in c for c in log))
p, log, _ = self.run_script([draft()], "v9.8.7", tags="")
self.assertNotEqual(p.returncode, 0)
self.assertIn("push it first", p.stderr)
def test_dry_run_changes_nothing(self):
p, log, upload = self.run_script([draft()], "--dry-run", "v9.8.7")
self.assertEqual(p.returncode, 0, p.stderr + p.stdout)
self.assertIn('"page": "https://github.com/saphid/frame-control/releases/tag/v9.8.7"', p.stdout)
self.assertIsNone(upload)
self.assertTrue(all("-X" not in c for c in log))
if __name__ == "__main__":
unittest.main()