GRM-57: refactor: fully automate PR merge — no manual label/review needed
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"""Unit tests for scripts/configure_repo.py."""
|
||||
|
||||
import http
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import click
|
||||
@@ -8,181 +7,61 @@ import pytest
|
||||
|
||||
from gitea_runner_manager.config import BRANCH_PROTECTION_CONFIG, REPO_SETTINGS_CONFIG
|
||||
from gitea_runner_manager.exceptions import APIError
|
||||
from scripts.configure_repo import (
|
||||
_ensure_label_via_client,
|
||||
_ensure_label_via_tea,
|
||||
_handle_http_error,
|
||||
main,
|
||||
)
|
||||
from scripts.gitea_cli import TeaCLIError
|
||||
from scripts.configure_repo import _handle_http_error, main
|
||||
|
||||
|
||||
class TestHandleHttpError:
|
||||
def test_handle_http_error_403(self) -> None:
|
||||
err = APIError(http.HTTPStatus.FORBIDDEN, "Forbidden")
|
||||
with pytest.raises(click.ClickException) as exc:
|
||||
_handle_http_error(err)
|
||||
msg = str(exc.value)
|
||||
assert "admin rights" in msg
|
||||
assert "Settings → Branches" in msg
|
||||
def test_forbidden_raises_click_exception(self) -> None:
|
||||
with pytest.raises(click.ClickException, match="Forbidden"):
|
||||
_handle_http_error(APIError(403, "Forbidden"))
|
||||
|
||||
def test_handle_http_error_other(self) -> None:
|
||||
err = APIError(http.HTTPStatus.INTERNAL_SERVER_ERROR, "Internal Server Error")
|
||||
with pytest.raises(click.ClickException) as exc:
|
||||
_handle_http_error(err)
|
||||
assert str(http.HTTPStatus.INTERNAL_SERVER_ERROR) in str(exc.value)
|
||||
|
||||
def test_handle_http_error_json_parse_fails(self) -> None:
|
||||
err = APIError(http.HTTPStatus.BAD_GATEWAY, "bad gateway")
|
||||
with pytest.raises(click.ClickException) as exc:
|
||||
_handle_http_error(err)
|
||||
assert str(http.HTTPStatus.BAD_GATEWAY) in str(exc.value)
|
||||
|
||||
|
||||
class TestEnsureLabelViaTea:
|
||||
def test_creates_new_label(self) -> None:
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.return_value = [{"name": "bug"}]
|
||||
result = _ensure_label_via_tea(mock_tea, "owner/repo", "ready-to-merge", "2ecc71", "desc")
|
||||
assert result is True
|
||||
mock_tea.create_label.assert_called_once()
|
||||
|
||||
def test_label_already_exists(self) -> None:
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.return_value = [{"name": "ready-to-merge"}]
|
||||
result = _ensure_label_via_tea(mock_tea, "owner/repo", "ready-to-merge", "2ecc71", "desc")
|
||||
assert result is False
|
||||
mock_tea.create_label.assert_not_called()
|
||||
|
||||
def test_tea_error_falls_back_to_client(self) -> None:
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.side_effect = TeaCLIError("network error")
|
||||
with patch("scripts.configure_repo._ensure_label_via_client", return_value=True) as mock_fallback:
|
||||
result = _ensure_label_via_tea(mock_tea, "owner/repo", "ready-to-merge", "2ecc71", "desc")
|
||||
assert result is True
|
||||
mock_fallback.assert_called_once_with("ready-to-merge", "2ecc71", "desc")
|
||||
|
||||
def test_tea_not_installed_falls_back_to_client(self) -> None:
|
||||
"""When tea CLI is not installed (FileNotFoundError), fall back to GiteaClient."""
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.side_effect = FileNotFoundError("[Errno 2] No such file or directory: 'tea'")
|
||||
with patch("scripts.configure_repo._ensure_label_via_client", return_value=True) as mock_fallback:
|
||||
result = _ensure_label_via_tea(mock_tea, "owner/repo", "ready-to-merge", "2ecc71", "desc")
|
||||
assert result is True
|
||||
mock_fallback.assert_called_once_with("ready-to-merge", "2ecc71", "desc")
|
||||
|
||||
|
||||
class TestEnsureLabelViaClient:
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
||||
@patch("scripts.configure_repo.GiteaClient")
|
||||
def test_creates_label(self, mock_client_cls: MagicMock) -> None:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ensure_label.return_value = {"id": 1}
|
||||
mock_client_cls.return_value = mock_client
|
||||
result = _ensure_label_via_client("bug", "ff0000", "A bug")
|
||||
assert result is True
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
||||
@patch("scripts.configure_repo.GiteaClient")
|
||||
def test_label_already_exists(self, mock_client_cls: MagicMock) -> None:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ensure_label.return_value = None
|
||||
mock_client_cls.return_value = mock_client
|
||||
result = _ensure_label_via_client("bug", "ff0000", "A bug")
|
||||
assert result is False
|
||||
def test_other_error_raises_click_exception(self) -> None:
|
||||
with pytest.raises(click.ClickException, match="HTTP error"):
|
||||
_handle_http_error(APIError(500, "Server error"))
|
||||
|
||||
|
||||
class TestMain:
|
||||
def test_main_missing_token(self) -> None:
|
||||
with patch.dict("os.environ", {}, clear=True):
|
||||
with pytest.raises(click.ClickException) as exc:
|
||||
main()
|
||||
assert "REPO_TOKEN" in str(exc.value)
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
||||
@patch("scripts.configure_repo.TeaCLI")
|
||||
@patch("scripts.configure_repo.GiteaClient")
|
||||
def test_main_success(self, mock_client_cls: MagicMock, mock_tea_cls: MagicMock) -> None:
|
||||
def test_main_success(self, mock_client_cls: MagicMock) -> None:
|
||||
mock_client = MagicMock()
|
||||
mock_client_cls.return_value = mock_client
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.return_value = [] # No existing labels
|
||||
mock_tea_cls.return_value = mock_tea
|
||||
|
||||
main()
|
||||
with click.Context(click.Command("test")):
|
||||
main()
|
||||
|
||||
mock_client.ensure_branch_protection.assert_called_once_with("master", BRANCH_PROTECTION_CONFIG)
|
||||
mock_tea.create_label.assert_called_once()
|
||||
mock_client.update_repo_settings.assert_called_once_with(REPO_SETTINGS_CONFIG)
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
||||
@patch("scripts.configure_repo.TeaCLI")
|
||||
@patch("scripts.configure_repo.GiteaClient")
|
||||
def test_main_label_already_exists(self, mock_client_cls: MagicMock, mock_tea_cls: MagicMock) -> None:
|
||||
def test_main_api_error(self, mock_client_cls: MagicMock) -> None:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ensure_branch_protection.side_effect = APIError(403, "Forbidden")
|
||||
mock_client_cls.return_value = mock_client
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.return_value = [{"name": "ready-to-merge"}]
|
||||
mock_tea_cls.return_value = mock_tea
|
||||
|
||||
main()
|
||||
with pytest.raises(click.ClickException, match="Forbidden"):
|
||||
main()
|
||||
|
||||
mock_client.ensure_branch_protection.assert_called_once_with("master", BRANCH_PROTECTION_CONFIG)
|
||||
mock_tea.create_label.assert_not_called()
|
||||
mock_client.update_repo_settings.assert_called_once_with(REPO_SETTINGS_CONFIG)
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
||||
@patch("scripts.configure_repo.TeaCLI")
|
||||
@patch("scripts.configure_repo.GiteaClient")
|
||||
def test_main_tea_error_falls_back(self, mock_client_cls: MagicMock, mock_tea_cls: MagicMock) -> None:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ensure_label.return_value = {"id": 1}
|
||||
mock_client_cls.return_value = mock_client
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.side_effect = TeaCLIError("network error")
|
||||
mock_tea_cls.return_value = mock_tea
|
||||
|
||||
main()
|
||||
|
||||
mock_client.ensure_branch_protection.assert_called_once()
|
||||
# Fallback to GiteaClient for label creation
|
||||
mock_client.ensure_label.assert_called_once()
|
||||
mock_client.update_repo_settings.assert_called_once()
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
||||
@patch("scripts.configure_repo.TeaCLI")
|
||||
@patch("scripts.configure_repo.GiteaClient")
|
||||
def test_main_api_error(self, mock_client_cls: MagicMock, mock_tea_cls: MagicMock) -> None:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ensure_branch_protection.side_effect = APIError(http.HTTPStatus.FORBIDDEN, "Forbidden")
|
||||
mock_client_cls.return_value = mock_client
|
||||
mock_tea = MagicMock()
|
||||
mock_tea_cls.return_value = mock_tea
|
||||
|
||||
with pytest.raises(click.ClickException) as exc:
|
||||
@patch.dict("os.environ", {}, clear=True)
|
||||
def test_main_no_token(self) -> None:
|
||||
with pytest.raises(click.ClickException, match="REPO_TOKEN"):
|
||||
main()
|
||||
assert "HTTP" in str(exc.value)
|
||||
|
||||
|
||||
def test_main_module_block() -> None:
|
||||
with patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True):
|
||||
with patch("scripts.configure_repo.TeaCLI") as mock_tea_cls:
|
||||
with patch("scripts.configure_repo.GiteaClient") as mock_client_cls:
|
||||
mock_client = MagicMock()
|
||||
mock_client_cls.return_value = mock_client
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_labels.return_value = []
|
||||
mock_tea_cls.return_value = mock_tea
|
||||
import scripts.configure_repo as cr
|
||||
with patch("scripts.configure_repo.GiteaClient") as mock_client_cls:
|
||||
mock_client = MagicMock()
|
||||
mock_client_cls.return_value = mock_client
|
||||
import scripts.configure_repo as cr
|
||||
|
||||
with open(cr.__file__) as f:
|
||||
source = f.read()
|
||||
# Remove __main__ block so exec doesn't call main() before we inject the mock
|
||||
source = source.replace('if __name__ == "__main__":\n main()\n', "")
|
||||
namespace = dict(cr.__dict__)
|
||||
exec(compile(source, cr.__file__, "exec"), namespace)
|
||||
namespace["GiteaClient"] = mock_client_cls
|
||||
namespace["TeaCLI"] = mock_tea_cls
|
||||
namespace["main"]()
|
||||
mock_client.ensure_branch_protection.assert_called_once_with("master", BRANCH_PROTECTION_CONFIG)
|
||||
mock_client.update_repo_settings.assert_called_once_with(REPO_SETTINGS_CONFIG)
|
||||
with open(cr.__file__) as f:
|
||||
source = f.read()
|
||||
source = source.replace('if __name__ == "__main__":\n main() # pragma: no cover\n', "")
|
||||
namespace = dict(cr.__dict__)
|
||||
exec(compile(source, cr.__file__, "exec"), namespace)
|
||||
namespace["GiteaClient"] = mock_client_cls
|
||||
namespace["main"]()
|
||||
mock_client.ensure_branch_protection.assert_called_once_with("master", BRANCH_PROTECTION_CONFIG)
|
||||
mock_client.update_repo_settings.assert_called_once_with(REPO_SETTINGS_CONFIG)
|
||||
|
||||
Reference in New Issue
Block a user