fix: revert review_pr.py to GiteaClient (tea v0.14.1 is interactive-only) (#70)

This commit is contained in:
emil
2026-06-22 07:11:20 +00:00
parent 28e61aa166
commit ad3c43bf8e
3 changed files with 89 additions and 67 deletions
+61 -55
View File
@@ -1,5 +1,6 @@
"""Unit tests for scripts/ci/review_pr.py."""
import http
import json
from unittest.mock import MagicMock, patch
@@ -7,8 +8,8 @@ import click
import pytest
from click.testing import CliRunner
from gitea_runner_manager.exceptions import APIError
from scripts.ci.review_pr import main, parse_comments
from scripts.gitea_cli import TeaCLIError
class TestParseComments:
@@ -59,25 +60,27 @@ class TestParseComments:
class TestMain:
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_successful_comment_review(self, mock_tea_cls: MagicMock) -> None:
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
@patch("scripts.ci.review_pr.GiteaClient")
def test_successful_comment_review(self, mock_client_cls: MagicMock) -> None:
mock_client = MagicMock()
mock_client.create_review.return_value = {"id": 42}
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(
main,
["5", "owner/repo", "--event", "COMMENT", "--body", "LGTM"],
)
assert result.exit_code == 0
assert "Review posted" in result.output
mock_tea.review_pr.assert_called_once_with("owner/repo", 5, event="COMMENT", body="LGTM")
assert "Review #42" in result.output
mock_client.create_review.assert_called_once_with("5", event="COMMENT", body="LGTM", comments=[])
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_successful_approve_review(self, mock_tea_cls: MagicMock) -> None:
@patch("scripts.ci.review_pr.GiteaClient")
def test_successful_approve_review(self, mock_client_cls: MagicMock) -> None:
"""APPROVE requires --checklist-confirmed and substantive body."""
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
mock_client = MagicMock()
mock_client.create_review.return_value = {"id": 7}
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(
main,
@@ -92,19 +95,20 @@ class TestMain:
],
)
assert result.exit_code == 0
mock_tea.review_pr.assert_called_once_with(
"owner/repo",
5,
assert "Review #7" in result.output
mock_client.create_review.assert_called_once_with(
"5",
event="APPROVE",
body="All 10 checklist categories verified. Architecture OK, tests pass.",
comments=[],
)
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_approve_without_checklist_confirmed_fails(self, mock_tea_cls: MagicMock) -> None:
@patch("scripts.ci.review_pr.GiteaClient")
def test_approve_without_checklist_confirmed_fails(self, mock_client_cls: MagicMock) -> None:
"""APPROVE without --checklist-confirmed is rejected."""
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
mock_client = MagicMock()
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(
main,
@@ -112,14 +116,14 @@ class TestMain:
)
assert result.exit_code != 0
assert "checklist" in result.output.lower()
mock_tea.review_pr.assert_not_called()
mock_client.create_review.assert_not_called()
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_approve_with_trivial_body_fails(self, mock_tea_cls: MagicMock) -> None:
@patch("scripts.ci.review_pr.GiteaClient")
def test_approve_with_trivial_body_fails(self, mock_client_cls: MagicMock) -> None:
"""APPROVE with trivial body (< 20 chars) and no comments is rejected."""
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
mock_client = MagicMock()
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(
main,
@@ -127,30 +131,32 @@ class TestMain:
)
assert result.exit_code != 0
assert "substantive" in result.output.lower()
mock_tea.review_pr.assert_not_called()
mock_client.create_review.assert_not_called()
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_successful_with_inline_comments(self, mock_tea_cls: MagicMock, tmp_path) -> None:
@patch("scripts.ci.review_pr.GiteaClient")
def test_successful_with_inline_comments(self, mock_client_cls: MagicMock, tmp_path) -> None:
comments = [{"path": "a.py", "body": "fix", "new_position": 1}]
f = tmp_path / "comments.json"
f.write_text(json.dumps(comments))
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
mock_client = MagicMock()
mock_client.create_review.return_value = {"id": 9}
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(
main,
["5", "owner/repo", "--comments-json", str(f)],
)
assert result.exit_code == 0
mock_tea.review_pr.assert_called_once_with("owner/repo", 5, event="COMMENT", body="")
mock_client.create_review.assert_called_once_with("5", event="COMMENT", body="", comments=comments)
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_successful_with_stdin_comments(self, mock_tea_cls: MagicMock) -> None:
@patch("scripts.ci.review_pr.GiteaClient")
def test_successful_with_stdin_comments(self, mock_client_cls: MagicMock) -> None:
comments = [{"path": "a.py", "body": "fix", "new_position": 1}]
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
mock_client = MagicMock()
mock_client.create_review.return_value = {"id": 11}
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(
main,
@@ -158,7 +164,7 @@ class TestMain:
input=json.dumps(comments),
)
assert result.exit_code == 0
mock_tea.review_pr.assert_called_once_with("owner/repo", 5, event="COMMENT", body="")
mock_client.create_review.assert_called_once_with("5", event="COMMENT", body="", comments=comments)
@patch.dict("os.environ", {"REPO_TOKEN": ""}, clear=True)
def test_missing_token_exits(self) -> None:
@@ -168,44 +174,44 @@ class TestMain:
assert "REPO_TOKEN" in result.output
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_no_body_or_comments_for_comment_event(self, mock_tea_cls: MagicMock) -> None:
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
@patch("scripts.ci.review_pr.GiteaClient")
def test_no_body_or_comments_for_comment_event(self, mock_client_cls: MagicMock) -> None:
mock_client = MagicMock()
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(main, ["5", "owner/repo", "--event", "COMMENT"])
assert result.exit_code == 1
assert "required" in result.output
mock_tea.review_pr.assert_not_called()
mock_client.create_review.assert_not_called()
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_no_body_or_comments_for_request_changes(self, mock_tea_cls: MagicMock) -> None:
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
@patch("scripts.ci.review_pr.GiteaClient")
def test_no_body_or_comments_for_request_changes(self, mock_client_cls: MagicMock) -> None:
mock_client = MagicMock()
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(main, ["5", "owner/repo", "--event", "REQUEST_CHANGES"])
assert result.exit_code == 1
assert "required" in result.output
mock_tea.review_pr.assert_not_called()
mock_client.create_review.assert_not_called()
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_tea_error_raises_click(self, mock_tea_cls: MagicMock) -> None:
mock_tea = MagicMock()
mock_tea.review_pr.side_effect = TeaCLIError("tea command failed")
mock_tea_cls.return_value = mock_tea
@patch("scripts.ci.review_pr.GiteaClient")
def test_api_error_raises_click(self, mock_client_cls: MagicMock) -> None:
mock_client = MagicMock()
mock_client.create_review.side_effect = APIError(http.HTTPStatus.INTERNAL_SERVER_ERROR, "server error")
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(main, ["5", "owner/repo", "--body", "x"])
assert result.exit_code == 1
assert "Failed to post review" in result.output
assert "HTTP" in result.output
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.ci.review_pr.TeaCLI")
def test_invalid_event_choice(self, mock_tea_cls: MagicMock) -> None:
mock_tea = MagicMock()
mock_tea_cls.return_value = mock_tea
@patch("scripts.ci.review_pr.GiteaClient")
def test_invalid_event_choice(self, mock_client_cls: MagicMock) -> None:
mock_client = MagicMock()
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(main, ["5", "owner/repo", "--event", "Bogus"])
assert result.exit_code != 0
mock_tea.review_pr.assert_not_called()
mock_client.create_review.assert_not_called()