Public Access
313 lines
13 KiB
Python
313 lines
13 KiB
Python
"""Unit tests for devx.tools.pr_status."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from devx.api_clients import GiteaClient
|
|
from devx.tools.pr_status import (
|
|
_get_pr_sha,
|
|
_get_symbol,
|
|
cli,
|
|
print_status,
|
|
wait_for_completion,
|
|
)
|
|
|
|
|
|
class TestGetSymbol:
|
|
def test_success(self) -> None:
|
|
assert _get_symbol("success") == "[OK]"
|
|
|
|
def test_failure(self) -> None:
|
|
assert _get_symbol("failure") == "[FAIL]"
|
|
|
|
def test_pending(self) -> None:
|
|
assert _get_symbol("pending") == "[..]"
|
|
|
|
def test_unknown(self) -> None:
|
|
assert _get_symbol("weird") == "[weird]"
|
|
|
|
|
|
class TestGetPrSha:
|
|
def test_returns_head_sha(self) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_pr.return_value = {"head": {"sha": "abc123"}}
|
|
assert _get_pr_sha(client, 42) == "abc123"
|
|
|
|
def test_returns_empty_when_missing(self) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_pr.return_value = {"head": {}}
|
|
assert _get_pr_sha(client, 42) == ""
|
|
|
|
|
|
class TestPrintStatus:
|
|
def test_no_statuses(self, capsys: pytest.CaptureFixture) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.return_value = []
|
|
result = print_status(client, "abc123")
|
|
assert result == "none"
|
|
|
|
def test_all_success(self, capsys: pytest.CaptureFixture) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.return_value = [
|
|
{"context": "CI / quality", "status": "success"},
|
|
{"context": "CI / lint", "status": "success"},
|
|
]
|
|
result = print_status(client, "abc123")
|
|
assert result == "success"
|
|
|
|
def test_has_failure(self, capsys: pytest.CaptureFixture) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.return_value = [
|
|
{"context": "CI / quality", "status": "success"},
|
|
{"context": "CI / lint", "status": "failure"},
|
|
]
|
|
result = print_status(client, "abc123")
|
|
assert result == "failure"
|
|
|
|
def test_pending(self, capsys: pytest.CaptureFixture) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.return_value = [
|
|
{"context": "CI / quality", "status": "pending"},
|
|
]
|
|
result = print_status(client, "abc123")
|
|
assert result == "pending"
|
|
|
|
def test_skipped_still_success(self, capsys: pytest.CaptureFixture) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.return_value = [
|
|
{"context": "CI / quality", "status": "success"},
|
|
{"context": "CI / molecule", "status": "skipped"},
|
|
]
|
|
result = print_status(client, "abc123")
|
|
assert result == "success"
|
|
|
|
|
|
class TestWaitForCompletion:
|
|
@patch("devx.tools.pr_status.time.sleep")
|
|
@patch("devx.tools.pr_status.time.time", side_effect=[0, 0, 100, 200])
|
|
def test_success_after_pending(
|
|
self, mock_time: MagicMock, mock_sleep: MagicMock, capsys: pytest.CaptureFixture
|
|
) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.side_effect = [
|
|
[{"context": "CI / quality", "status": "pending"}],
|
|
[{"context": "CI / quality", "status": "success"}],
|
|
]
|
|
result = wait_for_completion(client, "abc", timeout=600, interval=1)
|
|
assert result == "success"
|
|
|
|
@patch("devx.tools.pr_status.time.sleep")
|
|
@patch("devx.tools.pr_status.time.time", side_effect=[0, 0, 100, 200])
|
|
def test_failure_after_pending(self, mock_time: MagicMock, mock_sleep: MagicMock) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.side_effect = [
|
|
[{"context": "CI / quality", "status": "pending"}],
|
|
[{"context": "CI / quality", "status": "failure"}],
|
|
]
|
|
result = wait_for_completion(client, "abc", timeout=600, interval=1)
|
|
assert result == "failure"
|
|
|
|
@patch("devx.tools.pr_status.time.sleep")
|
|
@patch("devx.tools.pr_status.time.time", side_effect=[0, 700])
|
|
def test_timeout(self, mock_time: MagicMock, mock_sleep: MagicMock) -> None:
|
|
client = MagicMock(spec=GiteaClient)
|
|
client.get_commit_status.return_value = [{"context": "CI / quality", "status": "pending"}]
|
|
result = wait_for_completion(client, "abc", timeout=600, interval=1)
|
|
assert result == "pending"
|
|
|
|
|
|
class TestCli:
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
def test_no_token_raises(self, mock_subproc: MagicMock, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
for name in ("DEVELOPER_GITEA_API_TOKEN", "CI_GITEA_API_TOKEN", "CI_GITEA_TOKEN"):
|
|
monkeypatch.delenv(name, raising=False)
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli,
|
|
["--pr", "42"],
|
|
env={"DEVELOPER_GITEA_API_TOKEN": "", "CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""},
|
|
)
|
|
assert result.exit_code != 0
|
|
assert "CI_GITEA_TOKEN" in result.output
|
|
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
@patch("devx.tools.pr_status.REPO_OWNER", "")
|
|
@patch("devx.tools.pr_status.get_repo_name", side_effect=Exception("should not reach"))
|
|
def test_no_owner_raises(
|
|
self, mock_repo_name: MagicMock, mock_subproc: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--pr", "42"])
|
|
assert result.exit_code != 0
|
|
assert "owner" in result.output.lower()
|
|
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
def test_check_pr_status(
|
|
self, mock_client_cls: MagicMock, mock_subproc: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
client = mock_client_cls.return_value
|
|
client.get_pr.return_value = {"head": {"sha": "abc123"}}
|
|
client.get_commit_status.return_value = [
|
|
{"context": "CI / quality", "status": "success"},
|
|
]
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--pr", "42"])
|
|
assert result.exit_code == 0
|
|
assert "[OK]" in result.output
|
|
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
def test_check_sha_directly(
|
|
self, mock_client_cls: MagicMock, mock_subproc: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
client = mock_client_cls.return_value
|
|
client.get_commit_status.return_value = [
|
|
{"context": "CI / quality", "status": "success"},
|
|
]
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--sha", "abc123"])
|
|
assert result.exit_code == 0
|
|
assert "[OK]" in result.output
|
|
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
def test_failure_raises_exception(
|
|
self, mock_client_cls: MagicMock, mock_subproc: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
client = mock_client_cls.return_value
|
|
client.get_pr.return_value = {"head": {"sha": "abc123"}}
|
|
client.get_commit_status.return_value = [
|
|
{"context": "CI / quality", "status": "failure"},
|
|
]
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--pr", "42"])
|
|
assert result.exit_code != 0
|
|
assert "failed" in result.output.lower()
|
|
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
def test_auto_detect_branch(
|
|
self, mock_subprocess: MagicMock, mock_client_cls: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
mock_subprocess.return_value = MagicMock(returncode=0, stdout="feature-branch\n")
|
|
client = mock_client_cls.return_value
|
|
client.list_prs.return_value = [{"number": 42, "head": {"ref": "feature-branch"}}]
|
|
client.get_pr.return_value = {"head": {"sha": "abc123"}}
|
|
client.get_commit_status.return_value = [{"context": "CI / quality", "status": "success"}]
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, [])
|
|
assert result.exit_code == 0
|
|
assert "PR #42" in result.output
|
|
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
def test_auto_detect_no_pr_found(
|
|
self, mock_subprocess: MagicMock, mock_client_cls: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
mock_subprocess.return_value = MagicMock(returncode=0, stdout="feature-branch\n")
|
|
client = mock_client_cls.return_value
|
|
client.list_prs.return_value = []
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, [])
|
|
assert result.exit_code != 0
|
|
assert "No open PR" in result.output
|
|
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
def test_auto_detect_branch_error(
|
|
self, mock_subprocess: MagicMock, mock_client_cls: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
mock_subprocess.return_value = MagicMock(returncode=1, stderr="git error\n")
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, [])
|
|
assert result.exit_code != 0
|
|
assert "Could not detect" in result.output
|
|
|
|
@patch("devx.tools.pr_status.subprocess.run")
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
def test_no_sha_raises(
|
|
self, mock_client_cls: MagicMock, mock_subproc: MagicMock, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
client = mock_client_cls.return_value
|
|
client.get_pr.return_value = {"head": {}}
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--pr", "42"])
|
|
assert result.exit_code != 0
|
|
assert "SHA" in result.output
|
|
|
|
@patch("devx.tools.pr_status._get_current_branch_pr")
|
|
@patch("devx.tools.pr_status.time.sleep")
|
|
@patch("devx.tools.pr_status.time.time", side_effect=[0, 0, 100, 200])
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
def test_wait_success(
|
|
self,
|
|
mock_client_cls: MagicMock,
|
|
mock_time: MagicMock,
|
|
mock_sleep: MagicMock,
|
|
mock_branch_pr: MagicMock,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
client = mock_client_cls.return_value
|
|
client.get_pr.return_value = {"head": {"sha": "abc123"}}
|
|
client.get_commit_status.side_effect = [
|
|
[{"context": "CI / quality", "status": "pending"}],
|
|
[{"context": "CI / quality", "status": "success"}],
|
|
]
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--pr", "42", "--wait", "--timeout", "600", "--interval", "1"])
|
|
assert result.exit_code == 0
|
|
assert "[OK]" in result.output
|
|
|
|
@patch("devx.tools.pr_status._get_current_branch_pr")
|
|
@patch("devx.tools.pr_status.time.sleep")
|
|
@patch("devx.tools.pr_status.time.time", side_effect=[0, 700])
|
|
@patch("devx.tools.pr_status.GiteaClient")
|
|
def test_wait_timeout(
|
|
self,
|
|
mock_client_cls: MagicMock,
|
|
mock_time: MagicMock,
|
|
mock_sleep: MagicMock,
|
|
mock_branch_pr: MagicMock,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("CI_GITEA_TOKEN", "tok")
|
|
monkeypatch.setenv("DEVX_REPO_OWNER", "owner")
|
|
monkeypatch.setenv("DEVX_REPO_NAME", "repo")
|
|
client = mock_client_cls.return_value
|
|
client.get_pr.return_value = {"head": {"sha": "abc123"}}
|
|
client.get_commit_status.return_value = [{"context": "CI / quality", "status": "pending"}]
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--pr", "42", "--wait", "--timeout", "600", "--interval", "1"])
|
|
assert result.exit_code != 0
|
|
assert "timeout" in result.output.lower()
|