"""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"])