Files
grm/tests/unit/test_review_pr.py
T

171 lines
7.2 KiB
Python

"""Unit tests for scripts/review_pr.py."""
import http
import json
from unittest.mock import MagicMock, patch
import click
import pytest
from click.testing import CliRunner
from gitea_runner_manager.exceptions import APIError
from scripts.review_pr import main, parse_comments
class TestParseComments:
def test_parse_from_json_file(self, tmp_path) -> None:
comments = [{"path": "a.py", "body": "fix", "new_position": 1}]
f = tmp_path / "comments.json"
f.write_text(json.dumps(comments))
assert parse_comments(str(f), False) == comments
def test_parse_from_stdin(self) -> None:
comments = [{"path": "a.py", "body": "fix", "new_position": 1}]
with patch("scripts.review_pr.sys.stdin") as mock_stdin:
mock_stdin.read.return_value = json.dumps(comments)
assert parse_comments(None, True) == comments
def test_no_comments_returns_empty(self) -> None:
assert parse_comments(None, False) == []
def test_invalid_json_file_raises(self, tmp_path) -> None:
f = tmp_path / "comments.json"
f.write_text("not json{")
with pytest.raises(click.ClickException):
parse_comments(str(f), False)
def test_non_list_json_raises(self, tmp_path) -> None:
f = tmp_path / "comments.json"
f.write_text(json.dumps({"path": "a.py"}))
with pytest.raises(click.ClickException):
parse_comments(str(f), False)
def test_stdin_non_list_raises(self) -> None:
with patch("scripts.review_pr.sys.stdin") as mock_stdin:
mock_stdin.read.return_value = json.dumps({"path": "a.py"})
with pytest.raises(click.ClickException):
parse_comments(None, True)
def test_stdin_invalid_json_raises(self) -> None:
with patch("scripts.review_pr.sys.stdin") as mock_stdin:
mock_stdin.read.return_value = "not json{"
with pytest.raises(click.ClickException):
parse_comments(None, True)
def test_stdin_empty_returns_empty(self) -> None:
with patch("scripts.review_pr.sys.stdin") as mock_stdin:
mock_stdin.read.return_value = " "
assert parse_comments(None, True) == []
class TestMain:
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.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 #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.review_pr.GiteaClient")
def test_successful_approve_review(self, mock_client_cls: MagicMock) -> None:
mock_client = MagicMock()
mock_client.create_review.return_value = {"id": 7}
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(main, ["5", "owner/repo", "--event", "APPROVE"])
assert result.exit_code == 0
assert "Review #7" in result.output
mock_client.create_review.assert_called_once_with("5", event="APPROVE", body="", comments=[])
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.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_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_client.create_review.assert_called_once_with("5", event="COMMENT", body="", comments=comments)
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.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_client = MagicMock()
mock_client.create_review.return_value = {"id": 11}
mock_client_cls.return_value = mock_client
runner = CliRunner()
result = runner.invoke(
main,
["5", "owner/repo", "--comments-stdin"],
input=json.dumps(comments),
)
assert result.exit_code == 0
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:
runner = CliRunner()
result = runner.invoke(main, ["5", "owner/repo", "--body", "x"])
assert result.exit_code == 1
assert "REPO_TOKEN" in result.output
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.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_client.create_review.assert_not_called()
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.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_client.create_review.assert_not_called()
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.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 "HTTP" in result.output
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
@patch("scripts.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_client.create_review.assert_not_called()