143 lines
7.2 KiB
Python
143 lines
7.2 KiB
Python
"""Offline Gitea API and issue-reminder regression tests."""
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import Mock
|
|
from urllib.error import HTTPError
|
|
|
|
|
|
SPEC = importlib.util.spec_from_file_location("gitea_actions", Path(__file__).with_name("gitea_actions.py"))
|
|
ACTIONS = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(ACTIONS)
|
|
|
|
|
|
class APITests(unittest.TestCase):
|
|
def test_instance_url_token_and_raw_asset_request(self):
|
|
api = ACTIONS.GiteaAPI("https://git.example/subpath/", "owner/repo", "test-token")
|
|
response = Mock()
|
|
response.__enter__ = Mock(return_value=response)
|
|
response.__exit__ = Mock(return_value=False)
|
|
response.read.return_value = b'{"id": 1}'
|
|
api.opener = Mock()
|
|
api.opener.open.return_value = response
|
|
self.assertEqual({"id": 1}, api.request("POST", "/releases/1/assets?name=test.zip", b"archive", "application/octet-stream"))
|
|
request = api.opener.open.call_args.args[0]
|
|
self.assertEqual("https://git.example/subpath/api/v1/repos/owner/repo/releases/1/assets?name=test.zip", request.full_url)
|
|
self.assertEqual("token test-token", request.get_header("Authorization"))
|
|
self.assertEqual(b"archive", request.data)
|
|
self.assertEqual("application/octet-stream", request.get_header("Content-type"))
|
|
|
|
def test_only_explicit_404_is_missing_and_redirects_are_refused(self):
|
|
api = ACTIONS.GiteaAPI("https://git.example", "owner/repo", "test-token")
|
|
api.opener = Mock()
|
|
for status in (401, 403, 500, 302):
|
|
api.opener.open.side_effect = HTTPError(api.base, status, "error", {}, None)
|
|
with self.assertRaisesRegex(RuntimeError, f"HTTP {status}"):
|
|
api.request("GET", "/releases/tags/v28.0.0", allow_missing=True)
|
|
api.opener.open.side_effect = HTTPError(api.base, 404, "missing", {}, None)
|
|
self.assertIsNone(api.request("GET", "/releases/tags/v28.0.0", allow_missing=True))
|
|
self.assertIsNone(ACTIONS.NoRedirects().redirect_request(None, None, 302, "", {}, "https://elsewhere.example"))
|
|
|
|
def test_rejects_invalid_configuration(self):
|
|
for server, repository, token in [("http://git.example", "owner/repo", "x"),
|
|
("https://user:password@git.example", "owner/repo", "x"),
|
|
("https://git.example", "../owner/repo", "x"), ("https://git.example", "owner/repo", "")]:
|
|
with self.assertRaises(ValueError):
|
|
ACTIONS.GiteaAPI(server, repository, token)
|
|
|
|
|
|
class ReleaseTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
(self.root / "gitea.lock.json").write_text(json.dumps({"tag": "v28.0.0"}))
|
|
notes = self.root / ".github/release-notes/v28.0.0.md"
|
|
notes.parent.mkdir(parents=True)
|
|
notes.write_text("Reviewed notes", encoding="utf-8")
|
|
(self.root / "dist").mkdir()
|
|
for ext in (".zip", ".tar.gz"):
|
|
(self.root / "dist" / ("gitea-mail-templates-v28.0.0" + ext)).write_bytes(b"archive")
|
|
|
|
def test_existing_release_is_not_modified(self):
|
|
api = Mock()
|
|
api.request.return_value = {"id": 5}
|
|
with self.assertRaisesRegex(ValueError, "already exists"):
|
|
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
|
self.assertEqual(["GET"], [call.args[0] for call in api.request.call_args_list])
|
|
|
|
def test_publish_only_after_both_uploads_succeed(self):
|
|
api = Mock()
|
|
api.request.side_effect = [None, {"id": 5}, {"id": 6}, {"id": 7}, {"html_url": "https://git.example/release"}]
|
|
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
|
calls = api.request.call_args_list
|
|
self.assertEqual(["GET", "POST", "POST", "POST", "PATCH"], [c.args[0] for c in calls])
|
|
self.assertTrue(calls[1].args[2]["draft"])
|
|
self.assertEqual("Reviewed notes", calls[1].args[2]["body"])
|
|
self.assertEqual("/releases/5/assets?name=gitea-mail-templates-v28.0.0.zip", calls[2].args[1])
|
|
self.assertEqual("/releases/5/assets?name=gitea-mail-templates-v28.0.0.tar.gz", calls[3].args[1])
|
|
self.assertEqual({"draft": False}, calls[4].args[2])
|
|
|
|
def test_failed_upload_leaves_draft_unpublished(self):
|
|
api = Mock()
|
|
api.request.side_effect = [None, {"id": 5}, RuntimeError("upload failed")]
|
|
with self.assertRaisesRegex(RuntimeError, "upload failed"):
|
|
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
|
self.assertNotIn("PATCH", [c.args[0] for c in api.request.call_args_list])
|
|
|
|
def test_bad_version_or_missing_archive_fails_before_api_call(self):
|
|
api = Mock()
|
|
for version in ("v28.0.1", "v28.0.0-rc1", "../../x"):
|
|
with self.assertRaises(ValueError):
|
|
ACTIONS.publish_release(api, version, self.root)
|
|
(self.root / "dist/gitea-mail-templates-v28.0.0.zip").unlink()
|
|
with self.assertRaisesRegex(ValueError, "archive"):
|
|
ACTIONS.publish_release(api, "v28.0.0", self.root)
|
|
api.request.assert_not_called()
|
|
|
|
|
|
class IssueTests(unittest.TestCase):
|
|
def test_creates_release_documentation_issue(self):
|
|
api = Mock()
|
|
api.request.side_effect = [[], {"html_url": "https://git.example/issues/1"}]
|
|
ACTIONS.create_issue(api, "v28.1.0")
|
|
calls = api.request.call_args_list
|
|
self.assertEqual(["GET", "POST"], [c.args[0] for c in calls])
|
|
self.assertEqual("/issues", calls[-1].args[1])
|
|
payload = calls[-1].args[2]
|
|
self.assertIn("<!-- gitea-mail-templates:template-release:v28.1.0 -->", payload["body"])
|
|
self.assertIn("Documentation is maintained manually", payload["body"])
|
|
|
|
def test_reuses_closed_issue_on_later_page_and_ignores_pull_requests(self):
|
|
api = Mock()
|
|
marker = "<!-- gitea-mail-templates:template-release:v28.1.0 -->"
|
|
api.request.side_effect = [[{"body": marker, "pull_request": {"url": "pr"}}],
|
|
[{"body": marker, "state": "closed", "title": "Renamed", "html_url": "https://git.example/issues/1"}]]
|
|
ACTIONS.create_issue(api, "v28.1.0")
|
|
self.assertEqual(["GET", "GET"], [c.args[0] for c in api.request.call_args_list])
|
|
self.assertIn("state=all", api.request.call_args_list[0].args[1])
|
|
self.assertIn("page=2", api.request.call_args_list[1].args[1])
|
|
|
|
def test_other_release_versions_do_not_suppress_reminder(self):
|
|
api = Mock()
|
|
api.request.side_effect = [[{"body": "<!-- gitea-mail-templates:template-release:v28.0.0 -->"}], [],
|
|
{"html_url": "https://git.example/issues/2"}]
|
|
ACTIONS.create_issue(api, "v28.1.0")
|
|
payload = api.request.call_args.args[2]
|
|
self.assertIn("template-release:v28.1.0", payload["body"])
|
|
self.assertIn("READMEs", payload["body"])
|
|
|
|
def test_invalid_versions_fail_before_api_call(self):
|
|
api = Mock()
|
|
for version in ("28.1.0", "v28.1.0-rc1", "../../x"):
|
|
with self.assertRaises(ValueError):
|
|
ACTIONS.create_issue(api, version)
|
|
api.request.assert_not_called()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|