Files
grm/tests/unit/test_publish.py
T
Emil Simeonov fefddda715 GRM-20: refactor(scripts): centralize constants, API clients, and HTTP status codes
- Add shared config.py with API URLs, regexes, timeouts, pagination
- Add GiteaClient and VikunjaClient in api_clients.py with pooled sessions
- Add APIError exception for unified HTTP error handling
- Refactor all scripts to use shared modules and http.HTTPStatus
- Rewrite unit tests to mock clients and use HTTPStatus constants
- Add tests for api_clients and config modules
- Achieve 100% test coverage
2026-06-19 21:00:21 +02:00

151 lines
5.9 KiB
Python

"""Unit tests for scripts/publish.py."""
import http
from unittest.mock import MagicMock, patch
import click
import pytest
from click.testing import CliRunner
from scripts.publish import (
build_package,
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_raises(self, mock_run: MagicMock) -> None:
mock_run.return_value = MagicMock(returncode=1, stderr="build error")
with pytest.raises(click.ClickException) as exc:
build_package()
assert "build" in str(exc.value)
class TestPublishToPypi:
@patch("scripts.publish.subprocess.run")
def test_success(self, mock_run: MagicMock) -> 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]
@patch("scripts.publish.subprocess.run")
def test_failure_raises(self, mock_run: MagicMock) -> None:
mock_run.return_value = MagicMock(returncode=1, stderr="upload failed")
with pytest.raises(click.ClickException) as exc:
publish_to_pypi("pypi-tok")
assert "PyPI" in str(exc.value)
class TestMain:
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
@patch("scripts.publish.GiteaClient")
@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_client_cls: MagicMock,
) -> None:
runner = CliRunner()
result = runner.invoke(main, ["v1.0.0", "owner/repo"])
assert result.exit_code == 0
assert "Gitea release v1.0.0 created" in result.output
mock_build.assert_called_once()
mock_publish.assert_called_once_with("pypi-tok")
mock_client_cls.return_value.create_release.assert_called_once()
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok"}, clear=True)
@patch("scripts.publish.GiteaClient")
@patch("scripts.publish.build_package")
def test_without_pypi(
self,
mock_build: MagicMock,
mock_client_cls: MagicMock,
) -> None:
runner = CliRunner()
result = runner.invoke(main, ["v1.0.0", "owner/repo"])
assert result.exit_code == 0
mock_build.assert_called_once()
mock_client_cls.return_value.create_release.assert_called_once()
assert "PYPI_TOKEN not set" in result.output
@patch.dict("os.environ", {"GITEA_TOKEN": ""}, clear=True)
def test_missing_gitea_token_exits(self) -> None:
runner = CliRunner()
result = runner.invoke(main, ["v1.0.0", "owner/repo"])
assert result.exit_code == 1
assert "GITEA_TOKEN" in result.output
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
@patch("scripts.publish.GiteaClient")
@patch("scripts.publish.publish_to_pypi")
@patch("scripts.publish.build_package")
def test_build_failure_raises_click(
self, mock_build: MagicMock, mock_publish: MagicMock, mock_client_cls: MagicMock
) -> None:
mock_build.side_effect = click.ClickException("build failed")
runner = CliRunner()
result = runner.invoke(main, ["v1.0.0", "owner/repo"])
assert result.exit_code == 1
assert "build" in result.output
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
@patch("scripts.publish.GiteaClient")
@patch("scripts.publish.publish_to_pypi")
@patch("scripts.publish.build_package")
def test_publish_failure_raises_click(
self, mock_build: MagicMock, mock_publish: MagicMock, mock_client_cls: MagicMock
) -> None:
mock_publish.side_effect = click.ClickException("publish failed")
runner = CliRunner()
result = runner.invoke(main, ["v1.0.0", "owner/repo"])
assert result.exit_code == 1
assert "publish" in result.output
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
@patch("scripts.publish.GiteaClient")
@patch("scripts.publish.publish_to_pypi")
@patch("scripts.publish.build_package")
def test_release_failure_raises_click(
self, mock_build: MagicMock, mock_publish: MagicMock, mock_client_cls: MagicMock
) -> None:
mock_client = MagicMock()
from gitea_runner_manager.exceptions import APIError
mock_client.create_release.side_effect = APIError(http.HTTPStatus.INTERNAL_SERVER_ERROR, "server error")
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(main, ["v1.0.0", "owner/repo"])
assert result.exit_code == 1
assert "HTTP" in result.output
@patch.dict("os.environ", {"GITEA_TOKEN": "gitea-tok", "PYPI_TOKEN": "pypi-tok"})
@patch("scripts.publish.GiteaClient")
@patch("scripts.publish.publish_to_pypi")
@patch("scripts.publish.build_package")
def test_release_json_parse_failure(
self, mock_build: MagicMock, mock_publish: MagicMock, mock_client_cls: MagicMock
) -> None:
mock_client = MagicMock()
from gitea_runner_manager.exceptions import APIError
mock_client.create_release.side_effect = APIError(http.HTTPStatus.BAD_GATEWAY, "bad gateway")
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(main, ["v1.0.0", "owner/repo"])
assert result.exit_code == 1
assert str(http.HTTPStatus.BAD_GATEWAY) in result.output