open-swe/tests/github/test_github_ci.py
Adam Moussa ae1f883b4c
refactor: move tests into tests/<domain>/ layout
Applies the plan's C5 step: git mv every test per the domain-reorg
move-map (movemap-m50.txt) into tests/{agent,analyzer,auth,dashboard,
github,middleware,models,reviewer,sandbox,slack,tools,webhooks}/, plus
the 13 fork-only placements from the scoping report §2c (Atlassian
webhook tests -> tests/webhooks/, test_atlassian_connect.py and
test_auth_error_leak.py -> tests/auth/, jira/confluence util tests ->
tests/tools/, test_repo_binding_isolation.py -> tests/sandbox/,
bot-identity/autofix tests -> tests/github/).

Path-only move: the only content edits are parents[1] -> parents[2]
fixes in test_e2b_integration.py and test_daytona_integration.py,
required because their __file__-relative ROOT path gained one more
directory level in the move.

Monkeypatch retargets for these files were already completed in C4;
none remained outstanding here.
2026-07-17 14:42:45 -04:00

161 lines
5.4 KiB
Python

"""Unit tests for GitHub CI read helpers used by the auto-fix flow."""
from __future__ import annotations
from typing import Any
import httpx
import pytest
from agent.utils import github_ci
class _FakeResponse:
def __init__(self, payload: Any = None, error: bool = False) -> None:
self._payload = payload if payload is not None else {}
self._error = error
self.status_code = 200
self.headers: dict[str, str] = {}
def raise_for_status(self) -> None:
if self._error:
raise httpx.HTTPError("boom")
def json(self) -> Any:
return self._payload
class _FakeClient:
response: _FakeResponse = _FakeResponse({})
def __init__(self, **kwargs: Any) -> None:
pass
async def __aenter__(self) -> _FakeClient:
return self
async def __aexit__(self, *_: object) -> None:
return None
async def get(self, url: str, **_: Any) -> _FakeResponse:
return type(self).response
def _patch(monkeypatch: pytest.MonkeyPatch, payload: Any, error: bool = False) -> None:
_FakeClient.response = _FakeResponse(payload, error=error)
monkeypatch.setattr(github_ci.httpx, "AsyncClient", _FakeClient)
def test_branch_and_sha_from_check_run() -> None:
payload = {
"check_run": {
"head_sha": "deadbeef",
"check_suite": {"head_branch": "feat/x"},
}
}
assert github_ci.branch_from_check_payload(payload, "check_run") == "feat/x"
assert github_ci.head_sha_from_check_payload(payload, "check_run") == "deadbeef"
def test_branch_and_sha_from_workflow_run() -> None:
payload = {"workflow_run": {"head_sha": "abc", "head_branch": "main"}}
assert github_ci.branch_from_check_payload(payload, "workflow_run") == "main"
assert github_ci.head_sha_from_check_payload(payload, "workflow_run") == "abc"
def test_sha_from_status_event() -> None:
payload = {"sha": "sha1", "branches": [{"name": "b1"}]}
assert github_ci.head_sha_from_check_payload(payload, "status") == "sha1"
assert github_ci.branch_from_check_payload(payload, "status") == "b1"
def test_is_failing_ci_payload() -> None:
assert github_ci.is_failing_ci_payload(
{"check_run": {"status": "completed", "conclusion": "failure"}}, "check_run"
)
assert not github_ci.is_failing_ci_payload(
{"check_run": {"status": "completed", "conclusion": "success"}}, "check_run"
)
assert not github_ci.is_failing_ci_payload(
{"check_run": {"status": "in_progress", "conclusion": None}}, "check_run"
)
assert github_ci.is_failing_ci_payload({"state": "failure"}, "status")
assert not github_ci.is_failing_ci_payload({"state": "pending"}, "status")
@pytest.mark.asyncio
async def test_list_failing_check_runs_filters(monkeypatch: pytest.MonkeyPatch) -> None:
_patch(
monkeypatch,
{
"check_runs": [
{"name": "lint", "status": "completed", "conclusion": "failure"},
{"name": "test", "status": "completed", "conclusion": "success"},
{"name": "build", "status": "in_progress", "conclusion": None},
{"name": "Open SWE Auto-fix", "status": "completed", "conclusion": "failure"},
]
},
)
failing = await github_ci.list_failing_check_runs(owner="o", repo="r", ref="sha", token="t")
assert failing is not None
names = {c["name"] for c in failing}
assert names == {"lint"}
@pytest.mark.asyncio
async def test_list_failing_check_runs_returns_none_on_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_patch(monkeypatch, {}, error=True)
assert await github_ci.list_failing_check_runs(owner="o", repo="r", ref="s", token="t") is None
@pytest.mark.asyncio
async def test_names_failing_on_base(monkeypatch: pytest.MonkeyPatch) -> None:
# Both check-runs and statuses calls return the same fake payload here;
# only the check_runs shape is populated, statuses empty.
_patch(
monkeypatch,
{
"check_runs": [
{"name": "flaky", "status": "completed", "conclusion": "failure"},
],
"statuses": [],
},
)
names = await github_ci.names_failing_on_base(owner="o", repo="r", base_sha="base", token="t")
assert "flaky" in names
@pytest.mark.asyncio
async def test_names_failing_on_base_empty_when_no_base() -> None:
assert (
await github_ci.names_failing_on_base(owner="o", repo="r", base_sha="", token="t") == set()
)
@pytest.mark.asyncio
async def test_has_repo_write_permission_true(monkeypatch: pytest.MonkeyPatch) -> None:
_patch(monkeypatch, {"permission": "write"})
assert await github_ci.has_repo_write_permission(
owner="o", repo="r", username="alice", token="t"
)
@pytest.mark.asyncio
async def test_has_repo_write_permission_false_for_read(monkeypatch: pytest.MonkeyPatch) -> None:
_patch(monkeypatch, {"permission": "read"})
assert not await github_ci.has_repo_write_permission(
owner="o", repo="r", username="bob", token="t"
)
@pytest.mark.asyncio
async def test_has_repo_write_permission_fails_closed(monkeypatch: pytest.MonkeyPatch) -> None:
_patch(monkeypatch, {}, error=True)
assert not await github_ci.has_repo_write_permission(
owner="o", repo="r", username="bob", token="t"
)
assert not await github_ci.has_repo_write_permission(
owner="o", repo="r", username="", token="t"
)