This PR ensures that `arcade.core` does not show up anywhere in "user space". This is crucial for helping developers understand what objects are safe to use, and helps maintain a good developer experience. Specific changes: - `ToolAuthorizationContext` and `ToolContext` are now visible via `arcade.sdk` - `ToolCatalog` is now visible via `arcade.sdk` - `Toolkit` is now visible via `arcade.sdk` - `config` is now visible via `arcade.sdk.config`
359 lines
11 KiB
Python
359 lines
11 KiB
Python
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from arcade_github.tools.models import (
|
|
DiffSide,
|
|
ReviewCommentSubjectType,
|
|
)
|
|
from arcade_github.tools.pull_requests import (
|
|
create_reply_for_review_comment,
|
|
create_review_comment,
|
|
get_pull_request,
|
|
list_pull_request_commits,
|
|
list_pull_requests,
|
|
list_review_comments_on_pull_request,
|
|
update_pull_request,
|
|
)
|
|
from httpx import Response
|
|
|
|
from arcade.sdk.errors import RetryableToolError, ToolExecutionError
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_context():
|
|
context = AsyncMock()
|
|
context.authorization.token = "mock_token" # noqa: S105
|
|
return context
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_client():
|
|
with patch("arcade_github.tools.pull_requests.httpx.AsyncClient") as client:
|
|
yield client.return_value.__aenter__.return_value
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"func,args,status_code,json_response,expected_result,error_message",
|
|
[
|
|
(list_pull_requests, ("owner", "repo"), 200, [], '{"pull_requests": []}', None),
|
|
(
|
|
get_pull_request,
|
|
("owner", "repo", 1),
|
|
404,
|
|
{"message": "Not Found"},
|
|
None,
|
|
"Error accessing.*: Resource not found",
|
|
),
|
|
(
|
|
update_pull_request,
|
|
("owner", "repo", 1, "New Title"),
|
|
409,
|
|
{"message": "Conflict"},
|
|
None,
|
|
"Error accessing.*: Failed to process request",
|
|
),
|
|
(
|
|
list_pull_request_commits,
|
|
("owner", "repo", 1),
|
|
500,
|
|
{"message": "Internal Server Error"},
|
|
None,
|
|
"Error accessing.*: Failed to process request",
|
|
),
|
|
(
|
|
list_review_comments_on_pull_request,
|
|
("owner", "repo", 1),
|
|
403,
|
|
{"message": "API rate limit exceeded"},
|
|
None,
|
|
"Error accessing.*: Forbidden",
|
|
),
|
|
],
|
|
)
|
|
async def test_pull_request_functions(
|
|
mock_context,
|
|
mock_client,
|
|
func,
|
|
args,
|
|
status_code,
|
|
json_response,
|
|
expected_result,
|
|
error_message,
|
|
):
|
|
mock_client.get.return_value = mock_client.post.return_value = (
|
|
mock_client.patch.return_value
|
|
) = Response(status_code, json=json_response)
|
|
|
|
if error_message:
|
|
with pytest.raises(ToolExecutionError, match=error_message):
|
|
await func(mock_context, *args)
|
|
else:
|
|
result = await func(mock_context, *args)
|
|
assert result == expected_result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"func,args,json_response,expected_assertions",
|
|
[
|
|
(
|
|
list_pull_requests,
|
|
("owner", "repo"),
|
|
[
|
|
{
|
|
"number": 1,
|
|
"title": "Test PR",
|
|
"body": "This is a test PR",
|
|
"state": "open",
|
|
"html_url": "https://github.com/owner/repo/pull/1",
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-01T12:00:00Z",
|
|
"user": {"login": "testuser"},
|
|
"base": {"ref": "main"},
|
|
"head": {"ref": "feature-branch"},
|
|
}
|
|
],
|
|
["Test PR", "https://github.com/owner/repo/pull/1"],
|
|
),
|
|
(
|
|
update_pull_request,
|
|
("owner", "repo", 1, "Updated PR Title", "Updated PR body"),
|
|
{
|
|
"number": 1,
|
|
"title": "Updated PR Title",
|
|
"body": "Updated PR body",
|
|
"state": "open",
|
|
"html_url": "https://github.com/owner/repo/pull/1",
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-02T12:00:00Z",
|
|
"user": {"login": "testuser"},
|
|
},
|
|
["Updated PR Title", "Updated PR body"],
|
|
),
|
|
(
|
|
list_pull_request_commits,
|
|
("owner", "repo", 1),
|
|
[
|
|
{
|
|
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|
"commit": {
|
|
"author": {
|
|
"name": "Test Author",
|
|
"email": "author@example.com",
|
|
"date": "2023-05-01T12:00:00Z",
|
|
},
|
|
"message": "Test commit message",
|
|
},
|
|
}
|
|
],
|
|
["6dcb09b5b57875f334f61aebed695e2e4193db5e", "Test commit message"],
|
|
),
|
|
(
|
|
create_reply_for_review_comment,
|
|
("owner", "repo", 1, 42, "Thanks for the suggestion."),
|
|
{
|
|
"id": 123,
|
|
"body": "Thanks for the suggestion.",
|
|
"user": {"login": "testuser"},
|
|
"created_at": "2023-05-02T12:00:00Z",
|
|
"updated_at": "2023-05-02T12:00:00Z",
|
|
},
|
|
["Thanks for the suggestion.", "testuser"],
|
|
),
|
|
(
|
|
list_review_comments_on_pull_request,
|
|
("owner", "repo", 1),
|
|
[
|
|
{
|
|
"id": 1,
|
|
"body": "Great changes!",
|
|
"user": {"login": "reviewer1"},
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-01T12:00:00Z",
|
|
"path": "file1.txt",
|
|
"line": 5,
|
|
}
|
|
],
|
|
["Great changes!", "reviewer1", "file1.txt"],
|
|
),
|
|
(
|
|
get_pull_request,
|
|
("owner", "repo", 1, False, False),
|
|
{
|
|
"number": 1,
|
|
"title": "Test PR",
|
|
"body": "This is a test PR",
|
|
"state": "open",
|
|
"html_url": "https://github.com/owner/repo/pull/1",
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-01T12:00:00Z",
|
|
"user": {"login": "testuser"},
|
|
"base": {"ref": "main"},
|
|
"head": {"ref": "feature-branch"},
|
|
},
|
|
["Test PR", "https://github.com/owner/repo/pull/1"],
|
|
),
|
|
(
|
|
get_pull_request,
|
|
("owner", "repo", 1, True, False),
|
|
{
|
|
"number": 1,
|
|
"title": "Test PR",
|
|
"body": "This is a test PR",
|
|
"state": "open",
|
|
"html_url": "https://github.com/owner/repo/pull/1",
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-01T12:00:00Z",
|
|
"user": {"login": "testuser"},
|
|
"base": {"ref": "main"},
|
|
"head": {"ref": "feature-branch"},
|
|
"diff_content": "Sample diff content",
|
|
},
|
|
["Test PR", "https://github.com/owner/repo/pull/1", "diff_content"],
|
|
),
|
|
(
|
|
create_review_comment,
|
|
(
|
|
"owner",
|
|
"repo",
|
|
1,
|
|
"Great changes!",
|
|
"file1.txt",
|
|
"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|
1,
|
|
2,
|
|
DiffSide.RIGHT,
|
|
None,
|
|
ReviewCommentSubjectType.LINE,
|
|
),
|
|
{
|
|
"id": 1,
|
|
"body": "Great changes!",
|
|
"path": "file1.txt",
|
|
"line": 2,
|
|
"side": "RIGHT",
|
|
"commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|
"user": {"login": "testuser"},
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-01T12:00:00Z",
|
|
"html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
|
|
},
|
|
["Great changes!", "file1.txt", "6dcb09b5b57875f334f61aebed695e2e4193db5e"],
|
|
),
|
|
],
|
|
)
|
|
async def test_pull_request_functions_success(
|
|
mock_context, mock_client, func, args, json_response, expected_assertions
|
|
):
|
|
mock_client.get.return_value = mock_client.post.return_value = (
|
|
mock_client.patch.return_value
|
|
) = Response(200, json=json_response)
|
|
|
|
result = await func(mock_context, *args)
|
|
for assertion in expected_assertions:
|
|
assert assertion in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_review_comment_file_subject_type(mock_context, mock_client):
|
|
mock_client.post.return_value = Response(
|
|
200,
|
|
json={
|
|
"id": 1,
|
|
"body": "File comment",
|
|
"path": "file1.txt",
|
|
"commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|
"user": {"login": "testuser"},
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-01T12:00:00Z",
|
|
"html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
|
|
},
|
|
)
|
|
|
|
result = await create_review_comment(
|
|
mock_context,
|
|
"owner",
|
|
"repo",
|
|
1,
|
|
"File comment",
|
|
"file1.txt",
|
|
"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|
subject_type=ReviewCommentSubjectType.FILE,
|
|
)
|
|
|
|
assert "File comment" in result
|
|
assert "file1.txt" in result
|
|
assert "6dcb09b5b57875f334f61aebed695e2e4193db5e" in result
|
|
assert "start_line" not in mock_client.post.call_args[1]["json"]
|
|
assert "end_line" not in mock_client.post.call_args[1]["json"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_review_comment_missing_commit_id(mock_context, mock_client):
|
|
mock_client.get.return_value = Response(
|
|
200,
|
|
json=[{"sha": "latest_commit_sha"}],
|
|
)
|
|
mock_client.post.return_value = Response(
|
|
200,
|
|
json={
|
|
"id": 1,
|
|
"body": "Comment with auto-fetched commit ID",
|
|
"path": "file1.txt",
|
|
"commit_id": "latest_commit_sha",
|
|
"user": {"login": "testuser"},
|
|
"created_at": "2023-05-01T12:00:00Z",
|
|
"updated_at": "2023-05-01T12:00:00Z",
|
|
"html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
|
|
},
|
|
)
|
|
|
|
result = await create_review_comment(
|
|
mock_context,
|
|
"owner",
|
|
"repo",
|
|
1,
|
|
"Comment with auto-fetched commit ID",
|
|
"file1.txt",
|
|
start_line=1,
|
|
end_line=2,
|
|
)
|
|
|
|
assert "Comment with auto-fetched commit ID" in result
|
|
assert "latest_commit_sha" in result
|
|
assert mock_client.get.called
|
|
assert mock_client.post.called
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_review_comment_invalid_input(mock_context, mock_client):
|
|
with pytest.raises(
|
|
RetryableToolError, match="'start_line' and 'end_line' parameters are required"
|
|
):
|
|
await create_review_comment(
|
|
mock_context,
|
|
"owner",
|
|
"repo",
|
|
1,
|
|
"Invalid comment",
|
|
"file1.txt",
|
|
subject_type=ReviewCommentSubjectType.LINE,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_review_comment_no_commits(mock_context, mock_client):
|
|
mock_client.get.return_value = Response(200, json=[])
|
|
|
|
with pytest.raises(RetryableToolError, match="Failed to get the latest commit SHA"):
|
|
await create_review_comment(
|
|
mock_context,
|
|
"owner",
|
|
"repo",
|
|
1,
|
|
"Comment with no commits",
|
|
"file1.txt",
|
|
start_line=1,
|
|
end_line=2,
|
|
)
|