"""Tests for reviewer final-report schema verification (#391).""" import sys import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from review_final_report_schema import assess_review_final_report_schema # noqa: E402 def _minimal_review_report(**overrides): lines = [ "## Controller Handoff", "", "- Task: review PR #203", "- Repo: Scaled-Tech-Consulting/Gitea-Tools", "- Role: reviewer", "- Identity: sysadmin / prgs-reviewer", "- Issue/PR: #182 / PR #203", "- Branch/SHA: feat/x @ 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9", "- Files changed: review_proofs.py", "- Validation: pass: pytest tests/test_review_proofs.py -q in branches/review-203", "- Reviewed head SHA: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9", "- Final live head SHA before approval: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9", "- Final live head SHA before merge: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9", "- Push occurred during validation: no", "- Mutations: review only", "- File edits by reviewer: none", "- Worktree/index mutations: none", "- Git ref mutations: git fetch prgs master", "- MCP/Gitea mutations: review submitted", "- Review mutations: request_changes submitted", "- Merge mutations: none", "- Cleanup mutations: none", "- External-state mutations: none", "- Read-only diagnostics: gitea_view_pr, gitea_view_issue", "- Current status: PR open", "- Next actor: author", "- Next action: Fix", "- Next prompt: Fix", "- Blockers: none", "- Next: await author", "- Safety: no self-review; no self-merge", "- Selected PR: #203", "- Reviewer eligibility: eligible", "- Candidate head SHA: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9", "- Worktree path: branches/review-203", "- Worktree dirty: clean", "- Pinned reviewed head: none", "- Scratch worktree used: none", "- Unrelated local mutations: none", "- Review decision: request_changes", "- Merge result: not attempted", "- Linked issue: #182", "- Linked issue live status: open (gitea_view_issue)", "- Cleanup status: none", ] text = "\n".join(lines) for key, value in overrides.items(): prefix = f"- {key}:" replaced = False new_lines = [] for line in text.splitlines(): if line.strip().startswith(prefix): new_lines.append(f"{prefix} {value}") replaced = True else: new_lines.append(line) text = "\n".join(new_lines) if not replaced: text += f"\n{prefix} {value}" return text class TestReviewFinalReportSchema(unittest.TestCase): def test_clean_report_passes(self): result = assess_review_final_report_schema( _minimal_review_report(), linked_issue_lock={"issue_number": 182, "pr_number": 203}, ) self.assertFalse(result["blocked"]) self.assertIn(result["grade"], ("A", "downgraded")) def test_legacy_pinned_reviewed_head_blocks(self): report = _minimal_review_report(**{"Pinned reviewed head": "abc123"}) result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.legacy_stale_field", rule_ids) def test_reviewed_head_without_validation_blocks(self): report = _minimal_review_report().replace( "- Validation: pass: pytest tests/test_review_proofs.py -q in branches/review-203", "- Validation: not run", ) report += "\n- Pinned reviewed head: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9" result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.reviewed_head_without_validation", rule_ids) def test_merged_without_proof_blocks(self): report = _minimal_review_report() + "\n\nPR #203 merged successfully." result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.merged_without_proof", rule_ids) def test_issue_closed_without_live_proof_blocks(self): report = _minimal_review_report( **{ "Linked issue live status": "closed", "Read-only diagnostics": "gitea_view_pr only", } ) result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.issue_closed_without_live_proof", rule_ids) def test_full_suite_pass_with_ignored_tests_blocks(self): report = ( _minimal_review_report() + "\nFull test suite passed with 0 failed; some tests ignored." ) result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.full_suite_pass_ignored_tests", rule_ids) def test_blocked_handoff_replay_blocks(self): report = ( _minimal_review_report(**{"Blockers": "capability stop"}) + "\nSafe next action: run gitea_merge_pr to finish." ) result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.blocked_handoff_replay", rule_ids) def test_narrative_handoff_drift_blocks(self): report = ( _minimal_review_report() + "\n## Summary\nVerdict: APPROVE this PR." ) result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.narrative_handoff_drift", rule_ids) def test_pending_vs_approved_blocks(self): report = _minimal_review_report( **{"Review decision": "PENDING official review submitted approve"} ) result = assess_review_final_report_schema(report) rule_ids = [f["rule_id"] for f in result["findings"]] self.assertIn("reviewer.pending_vs_approved", rule_ids) def test_exported_from_review_proofs(self): from review_proofs import assess_review_final_report_schema as exported self.assertTrue(callable(exported)) class TestMcpValidateReviewFinalReport(unittest.TestCase): def test_mcp_tool_callable(self): import gitea_mcp_server result = gitea_mcp_server.gitea_validate_review_final_report( _minimal_review_report() ) self.assertIn("grade", result) self.assertIn("findings", result) self.assertFalse(result["blocked"])