170 lines
4.8 KiB
Python
170 lines
4.8 KiB
Python
from pathlib import Path
|
|
|
|
from scripts.burn_lane_issue_audit import (
|
|
PullSummary,
|
|
classify_issue,
|
|
collect_pull_summaries,
|
|
extract_issue_numbers,
|
|
match_prs,
|
|
render_report,
|
|
)
|
|
|
|
|
|
def test_extract_issue_numbers_handles_ranges_and_literals() -> None:
|
|
body = """
|
|
| #579 | CLOSED |
|
|
| #660-658 | Benchmark reports |
|
|
| #582, #627, #631 | Know Thy Father phases |
|
|
| #547-545 | Fleet progression |
|
|
"""
|
|
|
|
assert extract_issue_numbers(body) == [579, 660, 659, 658, 582, 627, 631, 547, 546, 545]
|
|
|
|
|
|
def test_match_prs_detects_issue_ref_in_pr_body() -> None:
|
|
pulls = [
|
|
PullSummary(
|
|
number=731,
|
|
title="docs: verify session harvest report",
|
|
state="open",
|
|
merged=False,
|
|
head="fix/session-harvest-report",
|
|
body="Refs #648",
|
|
url="https://forge.example/pr/731",
|
|
),
|
|
PullSummary(
|
|
number=732,
|
|
title="unrelated",
|
|
state="open",
|
|
merged=False,
|
|
head="fix/unrelated",
|
|
body="Refs #700",
|
|
url="https://forge.example/pr/732",
|
|
),
|
|
]
|
|
|
|
assert [pr.number for pr in match_prs(648, pulls)] == [731]
|
|
|
|
|
|
|
|
def test_open_issue_with_closed_unmerged_pr_stays_manual_review_with_history() -> None:
|
|
issue = {
|
|
"number": 648,
|
|
"title": "session harvest report",
|
|
"state": "open",
|
|
"html_url": "https://forge.example/issues/648",
|
|
}
|
|
row = classify_issue(
|
|
issue,
|
|
[
|
|
PullSummary(
|
|
number=731,
|
|
title="docs: add session harvest report",
|
|
state="closed",
|
|
merged=False,
|
|
head="fix/648",
|
|
body="Closes #648",
|
|
url="https://forge.example/pr/731",
|
|
)
|
|
],
|
|
)
|
|
|
|
assert row.classification == "needs_manual_review"
|
|
assert row.pr_summary == "closed PR #731"
|
|
|
|
|
|
|
|
def test_collect_pull_summaries_pages_until_empty(monkeypatch) -> None:
|
|
def fake_api_get(path: str, token: str):
|
|
if "state=open" in path:
|
|
return []
|
|
page = int(path.split("page=")[1])
|
|
if page <= 5:
|
|
return [
|
|
{
|
|
"number": page * 1000 + i,
|
|
"title": f"page {page} pr {i}",
|
|
"state": "closed",
|
|
"merged": False,
|
|
"head": {"ref": f"fix/{page}-{i}"},
|
|
"body": f"Refs #{page * 1000 + i}",
|
|
"html_url": f"https://forge.example/pr/{page * 1000 + i}",
|
|
}
|
|
for i in range(100)
|
|
]
|
|
if page == 6:
|
|
return [
|
|
{
|
|
"number": 900,
|
|
"title": "late page pr",
|
|
"state": "closed",
|
|
"merged": False,
|
|
"head": {"ref": "fix/900"},
|
|
"body": "Refs #900",
|
|
"html_url": "https://forge.example/pr/900",
|
|
}
|
|
]
|
|
return []
|
|
|
|
monkeypatch.setattr("scripts.burn_lane_issue_audit.api_get", fake_api_get)
|
|
|
|
pulls = collect_pull_summaries("timmy-home", "token")
|
|
|
|
assert any(pr.number == 900 for pr in pulls)
|
|
|
|
|
|
|
|
def test_render_report_calls_out_drift_and_candidates() -> None:
|
|
rows = [
|
|
{
|
|
"number": 579,
|
|
"title": "heartbeat",
|
|
"state": "closed",
|
|
"classification": "already_closed",
|
|
"pr_summary": "closed via merged PR #701",
|
|
},
|
|
{
|
|
"number": 648,
|
|
"title": "session harvest report",
|
|
"state": "open",
|
|
"classification": "closure_candidate",
|
|
"pr_summary": "merged PR #702",
|
|
},
|
|
{
|
|
"number": 645,
|
|
"title": "still active",
|
|
"state": "open",
|
|
"classification": "needs_manual_review",
|
|
"pr_summary": "no matching PR found",
|
|
},
|
|
]
|
|
|
|
report = render_report(
|
|
source_issue=662,
|
|
source_title="Burn lane empty",
|
|
referenced_rows=rows,
|
|
generated_at="2026-04-16T01:00:00Z",
|
|
)
|
|
|
|
assert "## Issue Body Drift" in report
|
|
assert "## Closure Candidates" in report
|
|
assert "#648" in report
|
|
assert "merged PR #702" in report
|
|
assert "#645" in report
|
|
assert "needs manual review" in report.lower()
|
|
|
|
|
|
def test_burn_lane_report_file_exists_with_required_sections() -> None:
|
|
text = Path("reports/production/2026-04-16-burn-lane-empty-audit.md").read_text(encoding="utf-8")
|
|
|
|
required = [
|
|
"# Burn Lane Empty Audit — timmy-home #662",
|
|
"## Source Snapshot",
|
|
"## Live Summary",
|
|
"## Issue Body Drift",
|
|
"## Closure Candidates",
|
|
"## Still Open / Needs Manual Review",
|
|
]
|
|
missing = [item for item in required if item not in text]
|
|
assert not missing, missing
|