Deterministic rules, verdict mapping, budget checks, idempotent Gitea publishing and the PR-Agent 0.47.0 integration, with unit tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
4.0 KiB
Python
100 lines
4.0 KiB
Python
import unittest
|
|
|
|
from tests.fakes import HEAD, review_data
|
|
from werkrevisor.verdict import (
|
|
InvalidModelAnswer,
|
|
Verdict,
|
|
assess_model,
|
|
gate_decision,
|
|
human_approval,
|
|
parse_model_review,
|
|
worst,
|
|
)
|
|
|
|
|
|
def assess(data, red_min_findings=2):
|
|
return assess_model(parse_model_review(data), red_min_findings)
|
|
|
|
|
|
class ExitCodeTest(unittest.TestCase):
|
|
def test_exit_codes(self):
|
|
self.assertEqual([v.exit_code for v in Verdict], [0, 10, 20, 30])
|
|
|
|
def test_worst(self):
|
|
self.assertIs(worst(Verdict.GREEN, Verdict.RED, Verdict.YELLOW), Verdict.RED)
|
|
self.assertIs(worst(Verdict.RED, Verdict.ERROR), Verdict.ERROR)
|
|
|
|
|
|
class ModelMappingTest(unittest.TestCase):
|
|
def test_safe_without_issues_is_green(self):
|
|
self.assertIs(assess(review_data())[0], Verdict.GREEN)
|
|
|
|
def test_changes_required_is_red(self):
|
|
self.assertIs(assess(review_data("changes_required"))[0], Verdict.RED)
|
|
|
|
def test_caution_is_yellow(self):
|
|
self.assertIs(assess(review_data("merge_with_caution"))[0], Verdict.YELLOW)
|
|
|
|
def test_high_risk_is_yellow(self):
|
|
self.assertIs(assess(review_data(risk="high"))[0], Verdict.YELLOW)
|
|
|
|
def test_security_concern_is_yellow(self):
|
|
self.assertIs(assess(review_data(security="SQL injection: ..."))[0], Verdict.YELLOW)
|
|
|
|
def test_one_issue_is_yellow_and_threshold_is_red(self):
|
|
self.assertIs(assess(review_data(issues=["a.py"]))[0], Verdict.YELLOW)
|
|
self.assertIs(assess(review_data(issues=["a.py", "b.py"]))[0], Verdict.RED)
|
|
self.assertIs(assess(review_data(issues=["a.py", "b.py"]), red_min_findings=0)[0], Verdict.YELLOW)
|
|
|
|
def test_invalid_answers(self):
|
|
for data in (None, {}, {"review": "x"},
|
|
{"review": {"risk_level": "low", "key_issues_to_review": []}},
|
|
{"review": {"merge_recommendation": "ship it", "risk_level": "low"}},
|
|
{"review": {"merge_recommendation": "safe_to_merge"}},
|
|
{"review": {"merge_recommendation": "safe_to_merge", "risk_level": "low",
|
|
"key_issues_to_review": "none"}}):
|
|
with self.subTest(data=data):
|
|
with self.assertRaises(InvalidModelAnswer):
|
|
parse_model_review(data)
|
|
|
|
def test_finding_location(self):
|
|
finding = parse_model_review(review_data(issues=["a.py"])).findings[0]
|
|
self.assertEqual(finding.location, "a.py:3-5")
|
|
|
|
|
|
class GateTest(unittest.TestCase):
|
|
def test_non_yellow_maps_directly(self):
|
|
for v in (Verdict.GREEN, Verdict.RED, Verdict.ERROR):
|
|
self.assertEqual(gate_decision(v, "pass_with_review_request", "alice", True).exit_code, v.exit_code)
|
|
|
|
def test_yellow_blocks_by_default(self):
|
|
self.assertEqual(gate_decision(Verdict.YELLOW, "block").exit_code, 10)
|
|
|
|
def test_yellow_with_pending_request(self):
|
|
self.assertEqual(gate_decision(Verdict.YELLOW, "pass_with_review_request", None, True).exit_code, 0)
|
|
self.assertEqual(gate_decision(Verdict.YELLOW, "pass_with_review_request", None, False).exit_code, 10)
|
|
self.assertEqual(gate_decision(Verdict.YELLOW, "block", None, True).exit_code, 10)
|
|
|
|
def test_yellow_unlocked_by_approval(self):
|
|
self.assertEqual(gate_decision(Verdict.YELLOW, "block", "alice").exit_code, 0)
|
|
|
|
|
|
class ApprovalTest(unittest.TestCase):
|
|
def review(self, **kw):
|
|
r = {"state": "APPROVED", "commit_id": HEAD, "user": {"login": "alice"}, "dismissed": False, "stale": False}
|
|
r.update(kw)
|
|
return r
|
|
|
|
def test_valid_approval(self):
|
|
self.assertEqual(human_approval([self.review()], HEAD, ["Alice"]), "alice")
|
|
|
|
def test_rejected_approvals(self):
|
|
for kw in ({"commit_id": "c" * 40}, {"state": "COMMENT"}, {"user": {"login": "mallory"}},
|
|
{"dismissed": True}, {"stale": True}):
|
|
with self.subTest(kw=kw):
|
|
self.assertIsNone(human_approval([self.review(**kw)], HEAD, ["alice"]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|