diff --git a/scripts/publish_zhenxun.py b/scripts/publish_zhenxun.py index 42d6a0b..3065ae0 100644 --- a/scripts/publish_zhenxun.py +++ b/scripts/publish_zhenxun.py @@ -13,9 +13,12 @@ REPOSITORY = "Shu-Ying/zhenxun_plugin_farm" BRANCH = "farm-preview" -def github(method, path, payload, token): +def github(method, path, payload, token, allow_missing=False): + url = f"https://api.github.com/repos/{REPOSITORY}" + if path: + url += f"/{path}" request = Request( - f"https://api.github.com/repos/{REPOSITORY}/{path}", + url, data=json.dumps(payload).encode() if payload is not None else None, headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json", @@ -26,10 +29,10 @@ def github(method, path, payload, token): with urlopen(request, timeout=30) as response: return json.load(response) except HTTPError as error: - if error.code == 404 and method == "GET": + if error.code == 404 and method == "GET" and allow_missing: return None raise RuntimeError( - f"GitHub request failed: HTTP {error.code}", + f"GitHub request failed: {method} {url}: HTTP {error.code}", ) from None @@ -47,9 +50,12 @@ def publish(package, confirmed=False, request=None): raise ValueError("Configure FARM_RELEASE_TOKEN in a protected job") call = request or (lambda method, path, data: github( method, path, data, token, + allow_missing=path.startswith(("git/ref/heads/", "contents/")), )) repository = call("GET", "", None) - if not repository or repository.get("default_branch") == BRANCH: + if not repository: + raise ValueError(f"Target repository is not accessible: {REPOSITORY}") + if repository.get("default_branch") == BRANCH: raise ValueError("Preview must not be the repository default branch") ref = call("GET", f"git/ref/heads/{BRANCH}", None) parent = ref["object"]["sha"] if ref else None diff --git a/tests/test_zhenxun_package.py b/tests/test_zhenxun_package.py index 5351398..d53280e 100644 --- a/tests/test_zhenxun_package.py +++ b/tests/test_zhenxun_package.py @@ -7,6 +7,7 @@ import tempfile import unittest from zipfile import ZipFile from unittest.mock import patch +from urllib.error import HTTPError ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) @@ -18,6 +19,40 @@ except ImportError: build = None +class PublisherRequestTests(unittest.TestCase): + def test_repository_url_has_no_trailing_slash(self): + from publish_zhenxun import github + with patch("publish_zhenxun.urlopen") as opened: + opened.return_value.__enter__.return_value.read.return_value = ( + b'{"default_branch": "main"}' + ) + self.assertEqual(github("GET", "", None, "test-token"), { + "default_branch": "main", + }) + request = opened.call_args.args[0] + self.assertEqual(request.full_url, + "https://api.github.com/repos/" + "Shu-Ying/zhenxun_plugin_farm") + + def test_repository_404_is_not_treated_as_missing_branch(self): + from publish_zhenxun import github + error = HTTPError("https://api.github.com", 404, "Not Found", + {}, None) + with patch("publish_zhenxun.urlopen", side_effect=error): + with self.assertRaisesRegex(RuntimeError, "HTTP 404"): + github("GET", "", None, "test-token") + + def test_optional_resource_404_returns_none(self): + from publish_zhenxun import github + error = HTTPError("https://api.github.com", 404, "Not Found", + {}, None) + with patch("publish_zhenxun.urlopen", side_effect=error): + self.assertIsNone(github( + "GET", "git/ref/heads/farm-preview", None, "test-token", + allow_missing=True, + )) + + class ZhenxunPackageTests(unittest.TestCase): def setUp(self): self.assertIsNotNone(build, "真寻打包功能未实现") @@ -136,6 +171,33 @@ class ZhenxunPackageTests(unittest.TestCase): "sha":"created", "force":False, }), calls) + def test_publish_creates_missing_preview_branch(self): + from publish_zhenxun import publish + package, _ = self.clean_package() + calls = [] + + def request(method, path, data): + calls.append((method, path, data)) + if path == "": + return {"default_branch": "main"} + if method == "GET": + return None + return {"sha": "created"} + + with patch.dict("os.environ", {"FARM_RELEASE_TOKEN": "unit-test"}): + self.assertTrue(publish(package, True, request)["published"]) + self.assertIn(("POST", "git/refs", { + "ref": "refs/heads/farm-preview", "sha": "created", + }), calls) + self.assertFalse(any(c[0] == "PATCH" for c in calls)) + + def test_inaccessible_repository_has_distinct_error(self): + from publish_zhenxun import publish + package, _ = self.clean_package() + with patch.dict("os.environ", {"FARM_RELEASE_TOKEN": "unit-test"}): + with self.assertRaisesRegex(ValueError, "not accessible"): + publish(package, True, lambda *args: None) + def test_symlink_duplicate_and_default_branch_rejected(self): from publish_zhenxun import publish from zipfile import ZipInfo