feat(robomp): added incoming PR review feature with classify and submit
- Added `review_pr` task that checks out PR head in a detached worktree, classifies rank/type/area, and posts a batched GitHub review as `event=COMMENT`. - Added four new host tools: `fetch_pr`, `classify_pr`, `pr_review_comment`, and `submit_pr_review`; review tools self-gate on `review_mode`, push/open-PR tools refuse when `review_mode` is set. - Added sqlite staging table `pr_review_comments` with `stage_review_comment`, `list_staged_review_comments`, and `clear_staged_review_comments` DAOs. - Routed `pull_request.opened/reopened/ready_for_review` to `review_pr` and extended `pull_request.closed` cleanup to any tracked PR regardless of author.
This commit is contained in:
@@ -109,6 +109,117 @@ def test_get_pull_request_parses_head_repo_and_author() -> None:
|
||||
assert pr.author == "robomp-bot"
|
||||
|
||||
|
||||
def test_get_pull_request_parses_title_and_body() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
assert request.url.path == "/repos/octo/widget/pulls/9"
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"number": 9,
|
||||
"html_url": "https://github.com/octo/widget/pull/9",
|
||||
"title": "Fix crash",
|
||||
"body": "Fixes #1",
|
||||
"head": {"ref": "fix", "repo": {"full_name": "fork/widget"}},
|
||||
"base": {"ref": "main"},
|
||||
"state": "open",
|
||||
"user": {"login": "alice"},
|
||||
},
|
||||
)
|
||||
|
||||
client = GitHubClient("tok", transport=httpx.MockTransport(handler))
|
||||
pr = _run_async(client.get_pull_request("octo/widget", 9))
|
||||
assert pr.title == "Fix crash"
|
||||
assert pr.body == "Fixes #1"
|
||||
|
||||
|
||||
def test_list_pr_files_parses_changed_file_summary() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
assert request.url.path == "/repos/octo/widget/pulls/9/files"
|
||||
assert request.url.params.get("per_page") == "100"
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[{"filename": "src/app.py", "status": "modified", "additions": 5, "deletions": 2}],
|
||||
)
|
||||
|
||||
client = GitHubClient("tok", transport=httpx.MockTransport(handler))
|
||||
files = _run_async(client.list_pr_files("octo/widget", 9))
|
||||
assert len(files) == 1
|
||||
assert files[0].path == "src/app.py"
|
||||
assert files[0].additions == 5
|
||||
assert files[0].deletions == 2
|
||||
|
||||
|
||||
def test_list_pr_files_paginates_past_first_page() -> None:
|
||||
seen_pages: list[str | None] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
assert request.url.path == "/repos/octo/widget/pulls/9/files"
|
||||
page = request.url.params.get("page")
|
||||
seen_pages.append(page)
|
||||
if page == "1":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[
|
||||
{
|
||||
"filename": f"src/file-{idx}.py",
|
||||
"status": "modified",
|
||||
"additions": 1,
|
||||
"deletions": 0,
|
||||
}
|
||||
for idx in range(100)
|
||||
],
|
||||
)
|
||||
assert page == "2"
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[{"filename": "src/final.py", "status": "added", "additions": 2, "deletions": 0}],
|
||||
)
|
||||
|
||||
client = GitHubClient("tok", transport=httpx.MockTransport(handler))
|
||||
files = _run_async(client.list_pr_files("octo/widget", 9))
|
||||
assert seen_pages == ["1", "2"]
|
||||
assert len(files) == 101
|
||||
assert files[-1].path == "src/final.py"
|
||||
|
||||
|
||||
def test_submit_pr_review_posts_comment_event_and_inline_comments() -> None:
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
import json
|
||||
|
||||
captured["path"] = request.url.path
|
||||
captured["body"] = json.loads(request.content)
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"id": 44,
|
||||
"user": {"login": "robomp-bot"},
|
||||
"body": "summary",
|
||||
"state": "COMMENTED",
|
||||
"submitted_at": "t",
|
||||
},
|
||||
)
|
||||
|
||||
client = GitHubClient("tok", transport=httpx.MockTransport(handler))
|
||||
review = _run_async(
|
||||
client.submit_pr_review(
|
||||
repo="octo/widget",
|
||||
pr_number=9,
|
||||
body="summary",
|
||||
event="COMMENT",
|
||||
comments=[{"path": "src/app.py", "line": 12, "side": "RIGHT", "body": "finding"}],
|
||||
)
|
||||
)
|
||||
assert review.id == 44
|
||||
assert captured["path"] == "/repos/octo/widget/pulls/9/reviews"
|
||||
assert captured["body"] == {
|
||||
"body": "summary",
|
||||
"event": "COMMENT",
|
||||
"comments": [{"path": "src/app.py", "line": 12, "side": "RIGHT", "body": "finding"}],
|
||||
}
|
||||
|
||||
|
||||
def test_204_no_content_returns_none() -> None:
|
||||
transport = httpx.MockTransport(lambda r: httpx.Response(204))
|
||||
client = GitHubClient("tok", transport=transport)
|
||||
|
||||
Reference in New Issue
Block a user