feat: require live blocker proof before skipping earlier PRs (Closes #318)
Add assess_prior_blocker_skip_proof to validate reviewer reports document live REQUEST_CHANGES blocker proof for skipped earlier open PRs, reject stale-memory skips when head changed after blocker, and require BLOCKER_STATUS_UNVERIFIED when review feedback cannot be fetched. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -121,4 +121,10 @@ def test_start_issue_template_references_work_issue_workflow():
|
||||
def test_create_issue_final_report_verifier_exported():
|
||||
from review_proofs import assess_create_issue_final_report
|
||||
|
||||
assert callable(assess_create_issue_final_report)
|
||||
assert callable(assess_create_issue_final_report)
|
||||
|
||||
|
||||
def test_prior_blocker_skip_verifier_exported():
|
||||
from review_proofs import assess_prior_blocker_skip_proof
|
||||
|
||||
assert callable(assess_prior_blocker_skip_proof)
|
||||
@@ -0,0 +1,139 @@
|
||||
"""Tests for prior-blocker skip proof verifier (#318)."""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from reviewer_blocker_skip import assess_prior_blocker_skip_proof # noqa: E402
|
||||
|
||||
HEAD_CURRENT = "0fdc8f582026b72a229d59a172c0a63ac4aaeaf9"
|
||||
HEAD_BLOCKER = "1111111111111111111111111111111111111111"
|
||||
|
||||
|
||||
def _good_blocker_skip_report(pr_number: int) -> str:
|
||||
return "\n".join([
|
||||
f"Skipped PR #{pr_number} due to prior REQUEST_CHANGES.",
|
||||
f"- PR number: #{pr_number}",
|
||||
f"- Current head SHA: {HEAD_CURRENT}",
|
||||
"- Blocking review decision: request_changes",
|
||||
f"- Blocking review head SHA: {HEAD_CURRENT}",
|
||||
"- Head unchanged since blocker",
|
||||
"- Reason remains blocked: unresolved review feedback at same head",
|
||||
"- Blocker revalidated live in this session via gitea_get_pr_review_feedback",
|
||||
])
|
||||
|
||||
|
||||
class TestPriorBlockerSkipProof(unittest.TestCase):
|
||||
def test_no_skips_passes(self):
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
"Selected PR #281 for review.",
|
||||
skipped_prs=[],
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_blocked_unchanged_with_live_proof_passes(self):
|
||||
report = _good_blocker_skip_report(280)
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_CURRENT,
|
||||
"head_changed_since_blocker": False,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_head_changed_after_blocker_blocks_skip(self):
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
_good_blocker_skip_report(280),
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_BLOCKER,
|
||||
"head_changed_since_blocker": True,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("head changed" in r for r in result["reasons"]))
|
||||
|
||||
def test_missing_live_proof_blocks(self):
|
||||
report = _good_blocker_skip_report(280).replace(
|
||||
"- Blocker revalidated live in this session via gitea_get_pr_review_feedback",
|
||||
"",
|
||||
)
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_CURRENT,
|
||||
"head_changed_since_blocker": False,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("live blocker proof" in r for r in result["reasons"]))
|
||||
|
||||
def test_blocker_unverified_classification_passes(self):
|
||||
report = "\n".join([
|
||||
"Skipped PR #280.",
|
||||
"Classification: BLOCKER_STATUS_UNVERIFIED",
|
||||
"Review feedback could not be fetched live in this session.",
|
||||
])
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocker_verified": False,
|
||||
}],
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_unverified_without_classification_blocks(self):
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
"Skipped PR #280 based on memory from last session.",
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocker_verified": False,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("BLOCKER_STATUS_UNVERIFIED" in r for r in result["reasons"]))
|
||||
|
||||
def test_missing_blocking_head_sha_blocks(self):
|
||||
report = _good_blocker_skip_report(280).replace(
|
||||
f"- Blocking review head SHA: {HEAD_CURRENT}",
|
||||
"",
|
||||
)
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_CURRENT,
|
||||
"head_changed_since_blocker": False,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
|
||||
|
||||
class TestExport(unittest.TestCase):
|
||||
def test_review_proofs_reexport(self):
|
||||
from review_proofs import assess_prior_blocker_skip_proof as exported
|
||||
|
||||
self.assertTrue(callable(exported))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user