Public Access
Post-merge / detect-type (push) Successful in 9s
Post-merge / validate-commit-msg (push) Successful in 9s
Post-merge / vikunja (push) Successful in 15s
Post-merge / configure-repo (push) Successful in 19s
Post-merge / release (push) Successful in 45s
Post-merge / sync-wiki (push) Successful in 46s
Post-merge / badges (push) Successful in 46s
Post-merge / publish (push) Successful in 17s
237 lines
10 KiB
Python
237 lines
10 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 (
|
|
_STANDARD_LABELS,
|
|
_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"] == 1
|
|
assert isinstance(config["status_check_contexts"], list)
|
|
assert "CI / quality (pull_request)" in config["status_check_contexts"]
|
|
assert config["block_admin_merge_override"] is True
|
|
|
|
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", {"CI_GITEA_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()
|
|
assert mock_client.ensure_label.call_count == len(_STANDARD_LABELS)
|
|
|
|
@patch.dict("os.environ", {"CI_GITEA_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="CI_GITEA_TOKEN"):
|
|
configure_repo(token="", owner="owner", repo="repo")
|
|
|
|
@patch.dict("os.environ", {"CI_GITEA_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)
|
|
# Labels are created regardless of custom configs
|
|
assert mock_client.ensure_label.call_count == len(_STANDARD_LABELS)
|
|
|
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_configure_repo_creates_all_standard_labels(self, mock_client_cls: MagicMock) -> None:
|
|
"""Verify all standard labels are ensured with correct names."""
|
|
mock_client = MagicMock()
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
configure_repo(token="tok", owner="owner", repo="repo")
|
|
|
|
created_names = [call.args[0] for call in mock_client.ensure_label.call_args_list]
|
|
expected_names = [lbl["name"] for lbl in _STANDARD_LABELS]
|
|
assert created_names == expected_names
|
|
|
|
|
|
class TestMain:
|
|
@patch.dict("os.environ", {"CI_GITEA_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()
|
|
assert mock_client.ensure_label.call_count == len(_STANDARD_LABELS)
|
|
|
|
@patch.dict("os.environ", {"CI_GITEA_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()
|
|
assert mock_client.ensure_label.call_count == len(_STANDARD_LABELS)
|
|
|
|
@patch.dict("os.environ", {"CI_GITEA_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 "CI_GITEA_TOKEN" in result.output
|
|
|
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.REPO_NAME", "")
|
|
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", {"CI_GITEA_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", {"CI_GITEA_TOKEN": "tok", "DEVX_REPO_NAME": "my-org/my-repo"}, 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] == "my-org" # owner is 3rd positional arg
|
|
assert call_args[0][3] == "my-repo" # repo is 4th positional arg
|
|
|
|
@patch.dict(
|
|
"os.environ",
|
|
{"CI_GITEA_TOKEN": "tok", "DEVX_REPO_NAME": "my-repo", "DEVX_REPO_OWNER": "my-org"},
|
|
clear=True,
|
|
)
|
|
@patch("devx.tools.configure_repo.REPO_OWNER", "my-org")
|
|
@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] == "my-org" # owner
|
|
assert call_args[0][3] == "my-repo" # repo
|
|
|
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok"}, clear=True)
|
|
@patch("devx.tools.configure_repo.REPO_NAME", "devx")
|
|
@patch("devx.tools.configure_repo.REPO_OWNER", "my-org")
|
|
@patch("devx.tools.configure_repo.GiteaClient")
|
|
def test_main_repo_from_pyproject(self, mock_client_cls: MagicMock) -> None:
|
|
"""When no env var is set, repo name should come from pyproject.toml."""
|
|
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] == "my-org" # owner
|
|
assert call_args[0][3] == "devx" # repo
|