Files
Gitea-Tools/tests/test_reviewer_mutation_capability_proof.py
T
sysadminandClaude Opus 4.8 2eaa001104 feat: require exact per-mutation capability proof in reviewer reports (Closes #405)
Adds mutation-capability table validation so review_pr cannot authorize
merge or delete mutations without their own exact capability rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-07 11:52:41 -04:00

142 lines
5.1 KiB
Python

"""Tests for exact per-mutation capability proof in reviewer reports (#405)."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from reviewer_mutation_capability_proof import assess_mutation_capability_proof
from review_proofs import assess_mutation_capability_proof as proofs_assess
from final_report_validator import assess_final_report_validator
REVIEW_ONLY = """
Review decision: approved
Mutation capability table:
- gitea_submit_pr_review | review_pr (gitea.pr.review) | submitted | resolved before review
"""
MERGE_PROVEN = """
Review decision: approved
Merge result: merged 0123456789ab
Mutation capability table:
- gitea_submit_pr_review | review_pr (gitea.pr.review) | submitted | before review
- gitea_merge_pr | merge_pr (gitea.pr.merge) | merged | resolved before merge
"""
MERGE_AND_DELETE_PROVEN = """
Review decision: approved
Merge result: merged 0123456789ab
Remote branch deleted: feat/issue-x
Mutation capability table:
- gitea_submit_pr_review | review_pr (gitea.pr.review) | submitted | before review
- gitea_merge_pr | merge_pr (gitea.pr.merge) | merged | before merge
- gitea_delete_branch | delete_branch (gitea.branch.delete) | deleted | before delete
"""
class TestModule(unittest.TestCase):
def test_review_only_with_capability_passes(self):
r = assess_mutation_capability_proof(REVIEW_ONLY)
self.assertTrue(r["proven"], r["reasons"])
def test_merge_with_exact_capability_passes(self):
r = assess_mutation_capability_proof(MERGE_PROVEN)
self.assertTrue(r["proven"], r["reasons"])
def test_merge_and_delete_fully_proven_passes(self):
r = assess_mutation_capability_proof(MERGE_AND_DELETE_PROVEN)
self.assertTrue(r["proven"], r["reasons"])
def test_review_pr_does_not_authorize_merge(self):
report = """
Review decision: approved
Merge result: merged 0123456789ab
Mutation capability table:
- gitea_submit_pr_review | review_pr (gitea.pr.review) | submitted | before review
"""
r = assess_mutation_capability_proof(report)
self.assertFalse(r["proven"])
self.assertTrue(any("merge" in x.lower() for x in r["reasons"]), r["reasons"])
def test_merge_pr_does_not_authorize_branch_deletion(self):
report = """
Review decision: approved
Merge result: merged 0123456789ab
Remote branch deleted: feat/issue-x
Mutation capability table:
- gitea_submit_pr_review | review_pr (gitea.pr.review) | submitted | before review
- gitea_merge_pr | merge_pr (gitea.pr.merge) | merged | before merge
"""
r = assess_mutation_capability_proof(report)
self.assertFalse(r["proven"])
self.assertTrue(any("delet" in x.lower() for x in r["reasons"]), r["reasons"])
def test_delete_skipped_when_capability_missing_passes(self):
report = """
Review decision: approved
Merge result: merged 0123456789ab
Branch deletion: skipped — delete_branch capability not available
Mutation capability table:
- gitea_submit_pr_review | review_pr (gitea.pr.review) | submitted | before review
- gitea_merge_pr | merge_pr (gitea.pr.merge) | merged | before merge
"""
r = assess_mutation_capability_proof(report)
self.assertTrue(r["proven"], r["reasons"])
def test_missing_table_when_merging_blocks(self):
report = """
Review decision: approved
Merge result: merged 0123456789ab
merge_pr gitea.pr.merge resolved
"""
r = assess_mutation_capability_proof(report)
self.assertFalse(r["proven"])
self.assertTrue(any("table" in x.lower() for x in r["reasons"]), r["reasons"])
def test_post_hoc_proof_blocks(self):
report = """
Review decision: approved
Merge result: merged 0123456789ab
Mutation capability table:
- gitea_merge_pr | merge_pr (gitea.pr.merge) | merged | capability resolved after merge
"""
r = assess_mutation_capability_proof(report)
self.assertFalse(r["proven"])
self.assertTrue(any("after" in x.lower() for x in r["reasons"]), r["reasons"])
def test_review_without_capability_blocks(self):
report = "Review decision: approved\nreview submitted\n"
r = assess_mutation_capability_proof(report)
self.assertFalse(r["proven"])
def test_no_mutation_no_requirement(self):
r = assess_mutation_capability_proof("Selected PR: #1\nSkipped, no action.")
self.assertTrue(r["proven"], r["reasons"])
class TestWiring(unittest.TestCase):
def test_review_proofs_wrapper_matches_module(self):
self.assertEqual(
proofs_assess(MERGE_PROVEN)["proven"],
assess_mutation_capability_proof(MERGE_PROVEN)["proven"],
)
def test_final_report_validator_flags_nearby_capability_merge(self):
report = """
## Controller Handoff
- Task: review_pr
Review decision: approved
Merge result: merged 0123456789ab
Mutation capability table:
- gitea_submit_pr_review | review_pr (gitea.pr.review) | submitted | before review
"""
result = assess_final_report_validator(report, "review_pr")
rule_ids = [f["rule_id"] for f in result.get("findings", [])]
self.assertIn("reviewer.mutation_capability_proof", rule_ids)
if __name__ == "__main__":
unittest.main()