mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
test(git): httpx-mock coverage for list_open_prs + get_pr_diff
Covers the inbound-PR read surface: list_open_prs normalization + fork/internal classification (and the recent _fetch_open_prs/_normalize_open_pr refactor), plus get_pr_diff's diff-media-type request — both with their safe-empty paths on missing token / GitHub error. The DB-backed paths (ingest/complete_review/pr_review_claim/supersede umbrella) are covered separately.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
"""GitService inbound-PR reads — list_open_prs + get_pr_diff (httpx fully mocked).
|
||||
|
||||
``list_open_prs`` is the discovery surface for the inbound-PR reviewer: it lists
|
||||
ALL open PRs and normalizes each (number, url, title, head_ref, head_sha,
|
||||
is_fork, user_login, author_association). ``get_pr_diff`` fetches a PR's unified
|
||||
diff read-only. Both return safe empties ([] / "") on a missing token or any
|
||||
GitHub error so the poll/claim never crash.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from roboco.services.git import GitService
|
||||
|
||||
_PR = 7
|
||||
|
||||
|
||||
def _service() -> GitService:
|
||||
session = MagicMock()
|
||||
session.execute = AsyncMock()
|
||||
svc = GitService(session)
|
||||
object.__setattr__(svc, "_token_for_project", AsyncMock(return_value="tok"))
|
||||
return svc
|
||||
|
||||
|
||||
def _resp(status_code: int, *, json_payload: Any = None, text: str = "") -> MagicMock:
|
||||
resp = MagicMock()
|
||||
resp.status_code = status_code
|
||||
resp.is_success = 200 <= status_code < 300 # noqa: PLR2004
|
||||
resp.json.return_value = json_payload
|
||||
resp.text = text
|
||||
return resp
|
||||
|
||||
|
||||
def _client(get_resp: MagicMock) -> MagicMock:
|
||||
client = MagicMock()
|
||||
client.__aenter__ = AsyncMock(return_value=client)
|
||||
client.__aexit__ = AsyncMock(return_value=False)
|
||||
client.get = AsyncMock(return_value=get_resp)
|
||||
return client
|
||||
|
||||
|
||||
def _patch_project() -> Any:
|
||||
fake = MagicMock()
|
||||
fake.get_by_slug = AsyncMock(
|
||||
return_value=MagicMock(git_url="https://github.com/acme/repo.git")
|
||||
)
|
||||
return patch("roboco.services.git.get_project_service", return_value=fake)
|
||||
|
||||
|
||||
def _pr(*, number: int, head_full: str, login: str, assoc: str) -> dict[str, Any]:
|
||||
return {
|
||||
"number": number,
|
||||
"html_url": f"https://github.com/acme/repo/pull/{number}",
|
||||
"title": f"PR {number}",
|
||||
"head": {
|
||||
"ref": "feature-x",
|
||||
"sha": "deadbeef",
|
||||
"repo": {"full_name": head_full},
|
||||
},
|
||||
"user": {"login": login},
|
||||
"author_association": assoc,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# list_open_prs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_open_prs_normalizes_and_flags_fork() -> None:
|
||||
svc = _service()
|
||||
payload = [
|
||||
_pr(number=7, head_full="forkowner/repo", login="corey", assoc="CONTRIBUTOR"),
|
||||
_pr(number=8, head_full="acme/repo", login="be-dev-1", assoc="MEMBER"),
|
||||
]
|
||||
client = _client(_resp(200, json_payload=payload))
|
||||
with (
|
||||
_patch_project(),
|
||||
patch("roboco.services.git.httpx.AsyncClient", return_value=client),
|
||||
):
|
||||
out = await svc.list_open_prs("roboco")
|
||||
assert len(out) == 2 # noqa: PLR2004
|
||||
fork, internal = out
|
||||
assert fork == {
|
||||
"number": 7,
|
||||
"url": "https://github.com/acme/repo/pull/7",
|
||||
"title": "PR 7",
|
||||
"head_ref": "feature-x",
|
||||
"head_sha": "deadbeef",
|
||||
"is_fork": True,
|
||||
"user_login": "corey",
|
||||
"author_association": "CONTRIBUTOR",
|
||||
}
|
||||
# Same-repo head → not a fork.
|
||||
assert internal["is_fork"] is False
|
||||
assert internal["author_association"] == "MEMBER"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_open_prs_empty_on_missing_token() -> None:
|
||||
svc = _service()
|
||||
object.__setattr__(svc, "_token_for_project", AsyncMock(return_value=None))
|
||||
with _patch_project():
|
||||
assert await svc.list_open_prs("roboco") == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_open_prs_empty_on_github_error() -> None:
|
||||
svc = _service()
|
||||
client = _client(_resp(403, text="forbidden"))
|
||||
with (
|
||||
_patch_project(),
|
||||
patch("roboco.services.git.httpx.AsyncClient", return_value=client),
|
||||
):
|
||||
assert await svc.list_open_prs("roboco") == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_pr_diff
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_pr_diff_returns_diff_text() -> None:
|
||||
svc = _service()
|
||||
diff = "diff --git a/x b/x\n+added line\n"
|
||||
client = _client(_resp(200, text=diff))
|
||||
with (
|
||||
_patch_project(),
|
||||
patch("roboco.services.git.httpx.AsyncClient", return_value=client),
|
||||
):
|
||||
out = await svc.get_pr_diff("roboco", _PR)
|
||||
assert out == diff
|
||||
call = client.get.await_args
|
||||
assert f"/pulls/{_PR}" in call.args[0]
|
||||
assert call.kwargs["headers"]["Accept"] == "application/vnd.github.v3.diff"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_pr_diff_empty_on_missing_token() -> None:
|
||||
svc = _service()
|
||||
object.__setattr__(svc, "_token_for_project", AsyncMock(return_value=None))
|
||||
with _patch_project():
|
||||
assert await svc.get_pr_diff("roboco", _PR) == ""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_pr_diff_empty_on_non_2xx() -> None:
|
||||
svc = _service()
|
||||
client = _client(_resp(404, text="not found"))
|
||||
with (
|
||||
_patch_project(),
|
||||
patch("roboco.services.git.httpx.AsyncClient", return_value=client),
|
||||
):
|
||||
assert await svc.get_pr_diff("roboco", _PR) == ""
|
||||
Reference in New Issue
Block a user