fix(git): dedupe check-runs per name so cancelled duplicates can't mask green

_classify_check_runs counted any completed cancelled check-run as failing.
The push + pull_request double-trigger leaves cancelled same-name
check-runs on the same head SHA next to the surviving run's green, so the
pr_pass gate saw permanent red on a genuinely green PR. Keep only the
newest (highest-id) run per check name before classifying.
This commit is contained in:
Renn F
2026-07-18 15:55:55 +02:00
parent 0f1e60fb95
commit d441fb2591
2 changed files with 92 additions and 4 deletions
@@ -116,6 +116,73 @@ async def test_failing_check_names_it() -> None:
assert out["head_sha"] == _SHA
@pytest.mark.asyncio
async def test_cancelled_duplicate_run_superseded_by_newer_green() -> None:
"""A superseded duplicate workflow run's cancelled check-run must not mask
the newer same-name run's green (the push + pull_request double-trigger
leaves both on the same head SHA)."""
checks = _resp(
200,
json_payload={
"check_runs": [
{
"id": 1,
"name": "tests",
"status": "completed",
"conclusion": "cancelled",
},
{
"id": 2,
"name": "tests",
"status": "completed",
"conclusion": "success",
},
]
},
)
client = _client(_pr_head_resp(), checks)
with (
_patch_project(),
patch("roboco.services.git.httpx.AsyncClient", return_value=client),
):
out = await _service().get_pr_ci_status("roboco", _PR)
assert out == {"state": "success", "head_sha": _SHA}
@pytest.mark.asyncio
async def test_newest_same_name_run_failing_still_fails() -> None:
"""Dedup keeps the newest run per name — a red re-run supersedes an older
green, never the reverse."""
checks = _resp(
200,
json_payload={
"check_runs": [
{
"id": 1,
"name": "tests",
"status": "completed",
"conclusion": "success",
},
{
"id": 2,
"name": "tests",
"status": "completed",
"conclusion": "failure",
},
]
},
)
client = _client(_pr_head_resp(), checks)
with (
_patch_project(),
patch("roboco.services.git.httpx.AsyncClient", return_value=client),
):
out = await _service().get_pr_ci_status("roboco", _PR)
assert out is not None
assert out["state"] == "failure"
assert out["failing_checks"] == ["tests"]
@pytest.mark.asyncio
async def test_still_running_check_is_pending() -> None:
checks = _resp(