GRM-20: feat: replace inline workflow scripts with tested Python modules
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""Unit tests for scripts/publish.py."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from scripts.publish import (
|
||||
GITEA_API,
|
||||
build_package,
|
||||
create_gitea_release,
|
||||
main,
|
||||
publish_to_pypi,
|
||||
)
|
||||
|
||||
|
||||
class TestBuildPackage:
|
||||
@patch("scripts.publish.subprocess.run")
|
||||
def test_success(self, mock_run: MagicMock) -> None:
|
||||
mock_run.return_value = MagicMock(returncode=0, stderr="")
|
||||
build_package()
|
||||
args, _ = mock_run.call_args
|
||||
assert args[0][1] == "-m"
|
||||
assert args[0][2] == "build"
|
||||
|
||||
@patch("scripts.publish.subprocess.run")
|
||||
def test_failure_exits(self, mock_run: MagicMock) -> None:
|
||||
mock_run.return_value = MagicMock(returncode=1, stderr="build error")
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
build_package()
|
||||
assert exc.value.code == 1
|
||||
|
||||
|
||||
class TestPublishToPypi:
|
||||
@patch("scripts.publish.subprocess.run")
|
||||
def test_success(self, mock_run: MagicMock, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
mock_run.return_value = MagicMock(returncode=0, stderr="")
|
||||
publish_to_pypi("pypi-tok")
|
||||
args, _ = mock_run.call_args
|
||||
assert "twine" in args[0]
|
||||
assert "pypi-tok" in args[0]
|
||||
captured = capsys.readouterr()
|
||||
assert "Published to PyPI" in captured.out
|
||||
|
||||
@patch("scripts.publish.subprocess.run")
|
||||
def test_failure_exits(self, mock_run: MagicMock) -> None:
|
||||
mock_run.return_value = MagicMock(returncode=1, stderr="upload failed")
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
publish_to_pypi("pypi-tok")
|
||||
assert exc.value.code == 1
|
||||
|
||||
|
||||
class TestCreateGiteaRelease:
|
||||
@patch("scripts.publish.requests.post")
|
||||
def test_success(self, mock_post: MagicMock) -> None:
|
||||
mock_response = MagicMock()
|
||||
mock_post.return_value = mock_response
|
||||
create_gitea_release("tok", "owner/repo", "v1.0.0")
|
||||
args, kwargs = mock_post.call_args
|
||||
assert GITEA_API in args[0]
|
||||
assert kwargs["json"]["tag_name"] == "v1.0.0"
|
||||
assert kwargs["json"]["draft"] is False
|
||||
assert kwargs["json"]["prerelease"] is False
|
||||
|
||||
@patch("scripts.publish.requests.post")
|
||||
def test_http_error_propagates(self, mock_post: MagicMock) -> None:
|
||||
mock_response = MagicMock()
|
||||
mock_response.raise_for_status.side_effect = requests.HTTPError("500")
|
||||
mock_post.return_value = mock_response
|
||||
with pytest.raises(requests.HTTPError):
|
||||
create_gitea_release("tok", "owner/repo", "v1.0.0")
|
||||
|
||||
|
||||
class TestMain:
|
||||
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
|
||||
@patch("scripts.publish.create_gitea_release")
|
||||
@patch("scripts.publish.publish_to_pypi")
|
||||
@patch("scripts.publish.build_package")
|
||||
def test_full_flow_with_pypi(
|
||||
self,
|
||||
mock_build: MagicMock,
|
||||
mock_publish: MagicMock,
|
||||
mock_release: MagicMock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
main(["publish.py", "v1.0.0", "owner/repo"])
|
||||
mock_build.assert_called_once()
|
||||
mock_publish.assert_called_once_with("pypi-tok")
|
||||
mock_release.assert_called_once_with("gitea-tok", "owner/repo", "v1.0.0")
|
||||
captured = capsys.readouterr()
|
||||
assert "Gitea release v1.0.0 created" in captured.out
|
||||
|
||||
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok"}, clear=True)
|
||||
@patch("scripts.publish.create_gitea_release")
|
||||
@patch("scripts.publish.build_package")
|
||||
def test_without_pypi(
|
||||
self,
|
||||
mock_build: MagicMock,
|
||||
mock_release: MagicMock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
main(["publish.py", "v1.0.0", "owner/repo"])
|
||||
mock_build.assert_called_once()
|
||||
mock_release.assert_called_once_with("gitea-tok", "owner/repo", "v1.0.0")
|
||||
captured = capsys.readouterr()
|
||||
assert "PYPI_TOKEN not set" in captured.out
|
||||
|
||||
@patch.dict("os.environ", {"GITEA_TOKEN": ""}, clear=True)
|
||||
def test_missing_gitea_token_exits(self) -> None:
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
main(["publish.py", "v1.0.0", "owner/repo"])
|
||||
assert exc.value.code == 1
|
||||
|
||||
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
|
||||
@patch("scripts.publish.create_gitea_release")
|
||||
@patch("scripts.publish.publish_to_pypi")
|
||||
@patch("scripts.publish.build_package")
|
||||
def test_build_failure_propagates(self, mock_build: MagicMock, *_: MagicMock) -> None:
|
||||
mock_build.side_effect = SystemExit(1)
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
main(["publish.py", "v1.0.0", "owner/repo"])
|
||||
assert exc.value.code == 1
|
||||
|
||||
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
|
||||
@patch("scripts.publish.create_gitea_release")
|
||||
@patch("scripts.publish.publish_to_pypi")
|
||||
@patch("scripts.publish.build_package")
|
||||
def test_publish_failure_propagates(self, mock_publish: MagicMock, *_: MagicMock) -> None:
|
||||
mock_publish.side_effect = SystemExit(1)
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
main(["publish.py", "v1.0.0", "owner/repo"])
|
||||
assert exc.value.code == 1
|
||||
|
||||
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
|
||||
@patch("scripts.publish.create_gitea_release")
|
||||
@patch("scripts.publish.publish_to_pypi")
|
||||
@patch("scripts.publish.build_package")
|
||||
def test_release_failure_propagates(self, mock_release: MagicMock, *_: MagicMock) -> None:
|
||||
mock_release.side_effect = requests.HTTPError("500")
|
||||
with pytest.raises(requests.HTTPError):
|
||||
main(["publish.py", "v1.0.0", "owner/repo"])
|
||||
Reference in New Issue
Block a user