171 lines
7.2 KiB
Python
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()
|