Public Access
196 lines
7.3 KiB
Python
196 lines
7.3 KiB
Python
"""Tests for devx.ci.fix_pr_title."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from devx.ci.fix_pr_title import cli
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _obl_infra_prefix(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Use OBL-INFRA prefix to match infra repo conventions."""
|
|
import re
|
|
|
|
monkeypatch.setattr("devx.ci.fix_pr_title.TASK_PREFIX", "OBL-INFRA")
|
|
monkeypatch.setattr("devx.ci.auto_merge.TASK_PREFIX", "OBL-INFRA")
|
|
monkeypatch.setattr("devx.ci.auto_merge.PR_TITLE_RE", re.compile(r"^OBL-INFRA-\d+:\s+.+"))
|
|
monkeypatch.setattr("devx.ci.auto_merge._TASK_ID_PREFIX_RE", re.compile(r"^OBL-INFRA-\d+:\s*"))
|
|
monkeypatch.setattr("devx.ci._shared.TASK_ID_RE", re.compile(r"OBL-INFRA-\d+"))
|
|
|
|
|
|
class TestFixPrTitle:
|
|
@patch("devx.ci.fix_pr_title.get_ci_token")
|
|
@patch("devx.ci.fix_pr_title.GiteaClient")
|
|
@patch("devx.ci.fix_pr_title.get_vikunja_title_optional")
|
|
def test_fixes_title_with_vikunja(
|
|
self,
|
|
mock_vikunja: MagicMock,
|
|
mock_gitea_cls: MagicMock,
|
|
mock_ci_token: MagicMock,
|
|
) -> None:
|
|
"""PR title is updated to match task ID + Vikunja title."""
|
|
mock_client = MagicMock()
|
|
mock_gitea_cls.return_value = mock_client
|
|
mock_client.get_pr.return_value = {
|
|
"number": 42,
|
|
"title": "Fix blackbox exporter",
|
|
"head": {"ref": "OBL-INFRA-458-blackbox-ipv4"},
|
|
}
|
|
mock_vikunja.return_value = "Fix blackbox exporter IPv4 config"
|
|
mock_ci_token.return_value = "token"
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "oblachno/infra", "--pr-number", "42"])
|
|
|
|
assert result.exit_code == 0
|
|
mock_client.update_pr.assert_called_once_with(42, {"title": "OBL-INFRA-458: Fix blackbox exporter IPv4 config"})
|
|
|
|
@patch("devx.ci.fix_pr_title.get_ci_token")
|
|
@patch("devx.ci.fix_pr_title.GiteaClient")
|
|
@patch("devx.ci.fix_pr_title.get_vikunja_title_optional")
|
|
def test_strips_conventional_commit_prefix_when_no_vikunja(
|
|
self,
|
|
mock_vikunja: MagicMock,
|
|
mock_gitea_cls: MagicMock,
|
|
mock_ci_token: MagicMock,
|
|
) -> None:
|
|
"""When Vikunja task not found, strips conventional-commit prefix from current title."""
|
|
mock_client = MagicMock()
|
|
mock_gitea_cls.return_value = mock_client
|
|
mock_client.get_pr.return_value = {
|
|
"number": 10,
|
|
"title": "fix: platform self-monitoring and fixes",
|
|
"head": {"ref": "OBL-INFRA-456-platform-fixes"},
|
|
}
|
|
mock_vikunja.return_value = None
|
|
mock_ci_token.return_value = "token"
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "oblachno/infra", "--pr-number", "10"])
|
|
|
|
assert result.exit_code == 0
|
|
mock_client.update_pr.assert_called_once_with(
|
|
10, {"title": "OBL-INFRA-456: platform self-monitoring and fixes"}
|
|
)
|
|
|
|
@patch("devx.ci.fix_pr_title.get_ci_token")
|
|
@patch("devx.ci.fix_pr_title.GiteaClient")
|
|
@patch("devx.ci.fix_pr_title.get_vikunja_title_optional")
|
|
def test_already_correct_title_no_update(
|
|
self,
|
|
mock_vikunja: MagicMock,
|
|
mock_gitea_cls: MagicMock,
|
|
mock_ci_token: MagicMock,
|
|
) -> None:
|
|
"""When PR title is already correct, no update is made."""
|
|
mock_client = MagicMock()
|
|
mock_gitea_cls.return_value = mock_client
|
|
mock_client.get_pr.return_value = {
|
|
"number": 5,
|
|
"title": "OBL-INFRA-100: Fix bug",
|
|
"head": {"ref": "OBL-INFRA-100-fix-bug"},
|
|
}
|
|
mock_vikunja.return_value = "Fix bug"
|
|
mock_ci_token.return_value = "token"
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "oblachno/infra", "--pr-number", "5"])
|
|
|
|
assert result.exit_code == 0
|
|
mock_client.update_pr.assert_not_called()
|
|
|
|
@patch("devx.ci.fix_pr_title.get_ci_token")
|
|
@patch("devx.ci.fix_pr_title.GiteaClient")
|
|
@patch("devx.ci.fix_pr_title.get_vikunja_title_optional")
|
|
def test_dry_run_no_update(
|
|
self,
|
|
mock_vikunja: MagicMock,
|
|
mock_gitea_cls: MagicMock,
|
|
mock_ci_token: MagicMock,
|
|
) -> None:
|
|
"""Dry run shows what would change without updating."""
|
|
mock_client = MagicMock()
|
|
mock_gitea_cls.return_value = mock_client
|
|
mock_client.get_pr.return_value = {
|
|
"number": 7,
|
|
"title": "Fix thing",
|
|
"head": {"ref": "OBL-INFRA-7-fix-thing"},
|
|
}
|
|
mock_vikunja.return_value = "Fix thing"
|
|
mock_ci_token.return_value = "token"
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "oblachno/infra", "--pr-number", "7", "--dry-run"])
|
|
|
|
assert result.exit_code == 0
|
|
mock_client.update_pr.assert_not_called()
|
|
|
|
@patch("devx.ci.fix_pr_title.get_ci_token")
|
|
@patch("devx.ci.fix_pr_title.GiteaClient")
|
|
def test_no_task_id_in_branch_exits_error(
|
|
self,
|
|
mock_gitea_cls: MagicMock,
|
|
mock_ci_token: MagicMock,
|
|
) -> None:
|
|
"""When branch has no task ID, exits with error."""
|
|
mock_client = MagicMock()
|
|
mock_gitea_cls.return_value = mock_client
|
|
mock_client.get_pr.return_value = {
|
|
"number": 1,
|
|
"title": "Some title",
|
|
"head": {"ref": "just-a-branch"},
|
|
}
|
|
mock_ci_token.return_value = "token"
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "oblachno/infra", "--pr-number", "1"])
|
|
|
|
assert result.exit_code != 0
|
|
mock_client.update_pr.assert_not_called()
|
|
|
|
@patch("devx.ci.fix_pr_title.get_ci_token")
|
|
def test_no_token_exits_error(self, mock_ci_token: MagicMock) -> None:
|
|
"""When CI token is not set, exits with error."""
|
|
mock_ci_token.side_effect = Exception("no token")
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "oblachno/infra", "--pr-number", "1"])
|
|
|
|
assert result.exit_code != 0
|
|
|
|
@patch("devx.ci.fix_pr_title.get_ci_token")
|
|
@patch("devx.ci.fix_pr_title.GiteaClient")
|
|
@patch("devx.ci.fix_pr_title.get_vikunja_title_optional")
|
|
def test_strips_task_id_prefix_from_vikunja_title(
|
|
self,
|
|
mock_vikunja: MagicMock,
|
|
mock_gitea_cls: MagicMock,
|
|
mock_ci_token: MagicMock,
|
|
) -> None:
|
|
"""When Vikunja title already has task ID prefix, it's stripped to avoid double prefix."""
|
|
mock_client = MagicMock()
|
|
mock_gitea_cls.return_value = mock_client
|
|
mock_client.get_pr.return_value = {
|
|
"number": 99,
|
|
"title": "Fix thing",
|
|
"head": {"ref": "OBL-INFRA-99-fix-thing"},
|
|
}
|
|
mock_vikunja.return_value = "OBL-INFRA-99: Fix thing"
|
|
mock_ci_token.return_value = "token"
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "oblachno/infra", "--pr-number", "99"])
|
|
|
|
assert result.exit_code == 0
|
|
mock_client.update_pr.assert_called_once_with(99, {"title": "OBL-INFRA-99: Fix thing"})
|
|
|
|
def test_invalid_repo_format_exits_error(self) -> None:
|
|
"""When repo is not in owner/name format, exits with error."""
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--repo", "invalid", "--pr-number", "1"])
|
|
assert result.exit_code != 0
|