Public Access
Post-merge / detect-type (push) Successful in 8s
Post-merge / validate-commit-msg (push) Successful in 13s
Post-merge / configure-repo (push) Successful in 25s
Post-merge / release (push) Successful in 50s
Post-merge / vikunja (push) Successful in 10s
Post-merge / sync-wiki (push) Successful in 41s
Post-merge / badges (push) Successful in 40s
200 lines
8.1 KiB
Python
200 lines
8.1 KiB
Python
"""Unit tests for devx.tools.configure_repo."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import click
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from devx.exceptions import APIError
|
|
from devx.tools.configure_repo import (
|
|
_default_branch_protection_config,
|
|
_default_repo_settings_config,
|
|
_handle_http_error,
|
|
configure_repo,
|
|
main,
|
|
)
|
|
|
|
|
|
class TestHandleHttpError:
|
|
def test_forbidden_raises_click_exception(self) -> None:
|
|
with pytest.raises(click.ClickException, match="Forbidden"):
|
|
_handle_http_error(APIError(403, "Forbidden"))
|
|
|
|
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 TestDefaultConfigs:
|
|
def test_default_branch_protection_config(self) -> None:
|
|
config = _default_branch_protection_config()
|
|
assert config["branch_name"] == "master"
|
|
assert config["enable_push"] is True
|
|
assert config["enable_push_whitelist"] is False
|
|
assert config["required_approvals"] == 0
|
|
assert isinstance(config["status_check_contexts"], list)
|
|
assert "CI / quality (pull_request)" in config["status_check_contexts"]
|
|
|
|
def test_default_repo_settings_config(self) -> None:
|
|
config = _default_repo_settings_config()
|
|
assert config["default_delete_branch_after_merge"] is True
|
|
|
|
def test_status_checks_from_env(self) -> None:
|
|
with patch.dict("os.environ", {"DEVX_STATUS_CHECKS": "check1, check2, check3"}):
|
|
config = _default_branch_protection_config()
|
|
assert config["status_check_contexts"] == ["check1", "check2", "check3"]
|
|
|
|
|
|
class TestConfigureRepo:
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_configure_repo_success(self, mock_client_cls: MagicMock) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
configure_repo(token="tok", owner="owner", repo="repo")
|
|
|
|
mock_client.ensure_branch_protection.assert_called_once()
|
|
mock_client.update_repo_settings.assert_called_once()
|
|
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_configure_repo_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
|
|
|
|
with pytest.raises(click.ClickException, match="Forbidden"):
|
|
configure_repo(token="tok", owner="owner", repo="repo")
|
|
|
|
def test_configure_repo_no_token(self) -> None:
|
|
with pytest.raises(click.ClickException, match="REPO_TOKEN"):
|
|
configure_repo(token="", owner="owner", repo="repo")
|
|
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_configure_repo_custom_configs(self, mock_client_cls: MagicMock) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
custom_bp = {
|
|
"branch_name": "develop",
|
|
"enable_push": True,
|
|
"enable_push_whitelist": True,
|
|
"push_whitelist_usernames": [],
|
|
"enable_status_check": True,
|
|
"status_check_contexts": ["CI / quality (pull_request)"],
|
|
"required_approvals": 2,
|
|
}
|
|
custom_rs = {"default_delete_branch_after_merge": False}
|
|
configure_repo(
|
|
token="tok",
|
|
owner="owner",
|
|
repo="repo",
|
|
branch="develop",
|
|
branch_protection_config=custom_bp,
|
|
repo_settings_config=custom_rs,
|
|
)
|
|
|
|
mock_client.ensure_branch_protection.assert_called_once_with("develop", custom_bp)
|
|
mock_client.update_repo_settings.assert_called_once_with(custom_rs)
|
|
|
|
|
|
class TestMain:
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok", "DEVX_REPO_NAME": "myrepo"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_main_success_with_env_repo(self, mock_client_cls: MagicMock) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, [])
|
|
assert result.exit_code == 0
|
|
mock_client.ensure_branch_protection.assert_called_once()
|
|
mock_client.update_repo_settings.assert_called_once()
|
|
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_main_success_with_cli_repo(self, mock_client_cls: MagicMock) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--repo", "myrepo", "--owner", "myorg"])
|
|
assert result.exit_code == 0
|
|
mock_client.ensure_branch_protection.assert_called_once()
|
|
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
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
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--repo", "myrepo"])
|
|
assert result.exit_code != 0
|
|
assert "Forbidden" in result.output
|
|
|
|
@patch.dict("os.environ", {}, clear=True)
|
|
def test_main_no_token(self) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--repo", "myrepo"])
|
|
assert result.exit_code != 0
|
|
assert "REPO_TOKEN" in result.output
|
|
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
|
def test_main_no_repo(self) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, [])
|
|
assert result.exit_code != 0
|
|
assert "Repository name not specified" in result.output
|
|
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_main_custom_branch(self, mock_client_cls: MagicMock) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--repo", "myrepo", "--branch", "develop"])
|
|
assert result.exit_code == 0
|
|
mock_client.ensure_branch_protection.assert_called_once()
|
|
args = mock_client.ensure_branch_protection.call_args
|
|
assert args[0][0] == "develop"
|
|
|
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok", "DEVX_REPO_NAME": "oblachno/infra"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_main_parses_owner_repo_from_env(self, mock_client_cls: MagicMock) -> None:
|
|
"""DEVX_REPO_NAME with 'owner/repo' format should be split."""
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, [])
|
|
assert result.exit_code == 0
|
|
# Verify GiteaClient was constructed with parsed owner and repo (positional)
|
|
call_args = mock_client_cls.call_args
|
|
assert call_args[0][2] == "oblachno" # owner is 3rd positional arg
|
|
assert call_args[0][3] == "infra" # repo is 4th positional arg
|
|
|
|
@patch.dict(
|
|
"os.environ",
|
|
{"REPO_TOKEN": "tok", "DEVX_REPO_NAME": "infra", "DEVX_REPO_OWNER": "oblachno"},
|
|
clear=True,
|
|
)
|
|
@patch("devx.tools.configure_repo.REPO_OWNER", "oblachno")
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_main_no_slash_when_owner_set_separately(self, mock_client_cls: MagicMock) -> None:
|
|
"""When DEVX_REPO_OWNER is set, DEVX_REPO_NAME should not be split."""
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, [])
|
|
assert result.exit_code == 0
|
|
call_args = mock_client_cls.call_args
|
|
assert call_args[0][2] == "oblachno" # owner
|
|
assert call_args[0][3] == "infra" # repo
|