Files
rune/tests/test_create_release.py

108 lines
3.9 KiB
Python

"""Tests for scripts/create_release.py — idempotent release creation (#192).
Pure tests cover payload construction and the tag-exists check; the dry-run
mode is exercised end-to-end with monkeypatched env (no API calls). The
script-level error path (finding 8, #192) runs the real entry point as a
subprocess against a refused port.
"""
from __future__ import annotations
import importlib.util
import json
import os
import socket
import subprocess
import sys
from pathlib import Path
from typing import Any
import pytest
ROOT = Path(__file__).resolve().parents[1]
def _load_module() -> Any:
spec = importlib.util.spec_from_file_location("create_release", ROOT / "scripts" / "create_release.py")
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
cr = _load_module()
class TestBuildReleasePayload:
def test_with_pr_context(self) -> None:
payload = cr.build_release_payload("0.56.0", "feat: widget", "Some body")
assert payload == {
"tag_name": "v0.56.0",
"target_commitish": "master",
"name": "Rune v0.56.0",
"body": "## feat: widget\n\nSome body",
"draft": False,
"prerelease": False,
}
def test_without_pr_context_falls_back_to_safety_net_body(self) -> None:
payload = cr.build_release_payload("0.56.0", "", "")
assert payload["tag_name"] == "v0.56.0"
assert "safety net" in payload["body"]
assert payload["draft"] is False
assert payload["prerelease"] is False
class TestReleaseExists:
def test_matches_tag_name(self) -> None:
releases = [{"tag_name": "v0.54.0"}, {"tag_name": "v0.55.0"}]
assert cr.release_exists(releases, "0.55.0") is True
assert cr.release_exists(releases, "0.56.0") is False
assert cr.release_exists([], "0.55.0") is False
class TestURLErrorCaughtAtScriptLevel:
"""Finding 8 (#192): __main__ catches only HTTPError, so a refused
connection (URLError) escapes and crashes with a traceback instead of
the consistent error path. Runs the real entry point as a subprocess."""
def test_refused_connection_exits_1_with_message_not_traceback(self, tmp_path: Path) -> None:
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
refused_port = s.getsockname()[1] # bound but never listening → ECONNREFUSED
env = dict(os.environ)
env.update(
RELEASE_VERSION="0.56.0",
GITEA_TOKEN="test-token",
RELEASE_SERVER_URL=f"http://127.0.0.1:{refused_port}",
RELEASE_REPO="rune/rune",
LIST_API_URL=f"http://127.0.0.1:{refused_port}/api/v1/repos/rune/rune/releases?limit=50",
RELEASE_API_URL=f"http://127.0.0.1:{refused_port}/api/v1/repos/rune/rune/releases",
)
env.pop("PYTEST_CURRENT_TEST", None)
proc = subprocess.run(
[sys.executable, str(ROOT / "scripts" / "create_release.py")],
env=env,
capture_output=True,
text=True,
timeout=60,
)
assert proc.returncode == 1, f"expected exit 1, stderr:\n{proc.stderr}"
assert proc.stderr.startswith("create_release: API failed")
assert "Traceback" not in proc.stderr
class TestDryRunMain:
def test_prints_payload_without_api_calls(
self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setenv("RELEASE_VERSION", "0.56.0")
monkeypatch.delenv("GITEA_TOKEN", raising=False)
monkeypatch.delenv("PR_TITLE", raising=False)
rc = cr.main(["--dry-run"])
assert rc == 0
payload = json.loads(capsys.readouterr().out)
assert payload["tag_name"] == "v0.56.0"
assert payload["target_commitish"] == "master"
assert "safety net" in payload["body"]