Skip to content

Commit 3d22ba3

Browse files
committed
fix(ci): harden BM Bossbot finalization
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 62229d9 commit 3d22ba3

2 files changed

Lines changed: 133 additions & 5 deletions

File tree

scripts/bm_bossbot_status.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,17 @@ def update_pull_request_body(*, token: str, repo: str, number: int, body: str) -
191191
)
192192

193193

194+
def get_pull_request_body(*, token: str, repo: str, number: int) -> str:
195+
response = _github_request(
196+
method="GET",
197+
path=f"/repos/{repo}/pulls/{number}",
198+
token=token,
199+
)
200+
if not isinstance(response, Mapping):
201+
raise SystemExit("GitHub API response for pull request was invalid")
202+
return _string(response.get("body"))
203+
204+
194205
def mark_pending(
195206
*,
196207
event_path: Path,
@@ -234,7 +245,8 @@ def finalize_review(
234245
review = {}
235246

236247
result = validate_review(review, expected_head_sha=event.head_sha)
237-
updated_body = upsert_summary_block(event.body, render_summary(review, result))
248+
current_body = get_pull_request_body(token=token, repo=event.repo, number=event.number)
249+
updated_body = upsert_summary_block(current_body, render_summary(review, result))
238250
update_pull_request_body(token=token, repo=event.repo, number=event.number, body=updated_body)
239251
set_commit_status(
240252
token=token,
@@ -255,9 +267,9 @@ def _github_request(
255267
method: str,
256268
path: str,
257269
token: str,
258-
payload: Mapping[str, Any],
270+
payload: Mapping[str, Any] | None = None,
259271
) -> Any:
260-
data = json.dumps(payload).encode("utf-8")
272+
data = None if payload is None else json.dumps(payload).encode("utf-8")
261273
request = urllib.request.Request(
262274
f"https://api.github.com{path}",
263275
data=data,
@@ -343,9 +355,7 @@ def finalize(
343355
Path,
344356
typer.Option(
345357
"--review",
346-
exists=True,
347358
dir_okay=False,
348-
readable=True,
349359
help="Structured BM Bossbot review JSON.",
350360
),
351361
],

tests/scripts/test_bm_bossbot_status.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1+
import json
2+
from pathlib import Path
3+
from typing import Mapping
4+
5+
import pytest
6+
from typer.testing import CliRunner
7+
18
from scripts import bm_bossbot_status
29

310

11+
def _event_payload(body: str = "Event snapshot body") -> dict[str, object]:
12+
return {
13+
"repository": {"full_name": "basicmachines-co/basic-memory"},
14+
"pull_request": {
15+
"number": 925,
16+
"body": body,
17+
"head": {"sha": "abc123"},
18+
},
19+
}
20+
21+
422
def test_status_script_is_uv_typer_entrypoint() -> None:
523
source = bm_bossbot_status.__file__
624
assert source is not None
@@ -84,3 +102,103 @@ def test_upsert_summary_block_replaces_existing_block() -> None:
84102
assert "New summary" in updated
85103
assert updated.startswith("Intro")
86104
assert updated.endswith("Footer")
105+
106+
107+
def test_finalize_review_fetches_current_pr_body_before_upserting(
108+
tmp_path: Path,
109+
monkeypatch: pytest.MonkeyPatch,
110+
) -> None:
111+
event_path = tmp_path / "event.json"
112+
review_path = tmp_path / "review.json"
113+
event_path.write_text(json.dumps(_event_payload()), encoding="utf-8")
114+
review_path.write_text(json.dumps(_review_payload()), encoding="utf-8")
115+
monkeypatch.setenv("GITHUB_TOKEN", "token")
116+
117+
updated_bodies: list[str] = []
118+
statuses: list[Mapping[str, str]] = []
119+
120+
def fake_get_pull_request_body(*, token: str, repo: str, number: int) -> str:
121+
assert token == "token"
122+
assert repo == "basicmachines-co/basic-memory"
123+
assert number == 925
124+
return "Current body edited while the workflow was running"
125+
126+
def fake_update_pull_request_body(*, token: str, repo: str, number: int, body: str) -> None:
127+
updated_bodies.append(body)
128+
129+
def fake_set_commit_status(
130+
*,
131+
token: str,
132+
repo: str,
133+
sha: str,
134+
payload: Mapping[str, str],
135+
) -> None:
136+
statuses.append(payload)
137+
138+
monkeypatch.setattr(bm_bossbot_status, "get_pull_request_body", fake_get_pull_request_body)
139+
monkeypatch.setattr(bm_bossbot_status, "update_pull_request_body", fake_update_pull_request_body)
140+
monkeypatch.setattr(bm_bossbot_status, "set_commit_status", fake_set_commit_status)
141+
142+
result = bm_bossbot_status.finalize_review(
143+
event_path=event_path,
144+
review_path=review_path,
145+
repo=None,
146+
run_url="https://github.com/basicmachines-co/basic-memory/actions/runs/1",
147+
token_env="GITHUB_TOKEN",
148+
)
149+
150+
assert result.approved is True
151+
assert "Current body edited while the workflow was running" in updated_bodies[0]
152+
assert "Event snapshot body" not in updated_bodies[0]
153+
assert statuses[0]["state"] == "success"
154+
155+
156+
def test_finalize_cli_marks_failure_when_review_file_is_missing(
157+
tmp_path: Path,
158+
monkeypatch: pytest.MonkeyPatch,
159+
) -> None:
160+
event_path = tmp_path / "event.json"
161+
missing_review_path = tmp_path / "missing-review.json"
162+
event_path.write_text(json.dumps(_event_payload(body="Current body")), encoding="utf-8")
163+
monkeypatch.setenv("GITHUB_TOKEN", "token")
164+
165+
updated_bodies: list[str] = []
166+
statuses: list[Mapping[str, str]] = []
167+
168+
def fake_get_pull_request_body(*, token: str, repo: str, number: int) -> str:
169+
return "Current body"
170+
171+
def fake_update_pull_request_body(*, token: str, repo: str, number: int, body: str) -> None:
172+
updated_bodies.append(body)
173+
174+
def fake_set_commit_status(
175+
*,
176+
token: str,
177+
repo: str,
178+
sha: str,
179+
payload: Mapping[str, str],
180+
) -> None:
181+
statuses.append(payload)
182+
183+
monkeypatch.setattr(bm_bossbot_status, "get_pull_request_body", fake_get_pull_request_body)
184+
monkeypatch.setattr(bm_bossbot_status, "update_pull_request_body", fake_update_pull_request_body)
185+
monkeypatch.setattr(bm_bossbot_status, "set_commit_status", fake_set_commit_status)
186+
187+
result = CliRunner().invoke(
188+
bm_bossbot_status.app,
189+
[
190+
"finalize",
191+
"--event",
192+
str(event_path),
193+
"--review",
194+
str(missing_review_path),
195+
"--repo",
196+
"basicmachines-co/basic-memory",
197+
"--run-url",
198+
"https://github.com/basicmachines-co/basic-memory/actions/runs/1",
199+
],
200+
)
201+
202+
assert result.exit_code == 1
203+
assert "BM Bossbot review output was invalid" in updated_bodies[0]
204+
assert statuses[0]["state"] == "failure"

0 commit comments

Comments
 (0)