Files
Gitea-Tools/tests/test_issue_acceptance_gate.py
sysadminandClaude Opus 4.8 33d01ba65e feat: add controller issue-acceptance gate (Closes #500)
Add issue_acceptance_gate validation for Controller Issue Acceptance
comments, final-report rules blocking merge-only issue completion claims,
documentation/templates, and regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 03:27:37 -04:00

183 lines
6.3 KiB
Python

"""Tests for controller issue-acceptance gate (#500)."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import issue_acceptance_gate # noqa: E402
from final_report_validator import assess_final_report_validator # noqa: E402
def _accepted_comment(**overrides):
lines = [
"## Controller Issue Acceptance",
"",
"STATE:",
"accepted",
"",
"WHO_IS_NEXT:",
"controller",
"",
"NEXT_ACTION:",
"Close the tracker follow-up after verification.",
"",
"NEXT_PROMPT:",
"Verify deployment checklist for issue #500 and post acceptance.",
"",
"ISSUE:",
"#500",
"",
"MERGED_PR:",
"#503",
"",
"MERGE_COMMIT:",
"0fdc8f582026b72a229d59a172c0a63ac4aaeaf9",
"",
"ACCEPTANCE_CRITERIA_CHECKED:",
"- [x] documentation added",
"- [x] validator tests added",
"",
"VALIDATION_REVIEWED:",
"pytest tests/test_issue_acceptance_gate.py -q",
"",
"CONTROLLER_DECISION:",
"accepted",
"",
"WHY:",
"All acceptance criteria satisfied with proof.",
"",
"MISSING_WORK:",
"none",
"",
"FOLLOW_UP_ISSUES:",
"none",
"",
"BLOCKERS:",
"none",
"",
"LAST_UPDATED_BY:",
"jcwalker3 / prgs-controller / 2026-07-08",
]
text = "\n".join(lines)
for key, value in overrides.items():
text = text.replace(f"{key}:\n", f"{key}:\n{value}\n", 1)
return text
def _rejection_comment(state="needs-tests"):
text = _accepted_comment()
text = text.replace("STATE:\naccepted", f"STATE:\n{state}")
text = text.replace(
"CONTROLLER_DECISION:\naccepted",
"CONTROLLER_DECISION:\nrejected",
)
text = text.replace(
"ACCEPTANCE_CRITERIA_CHECKED:\n- [x] documentation added\n- [x] validator tests added",
"ACCEPTANCE_CRITERIA_CHECKED:\n- [ ] regression tests for rejection paths",
)
text = text.replace(
"MISSING_WORK:\nnone",
"MISSING_WORK:\nAdd regression tests for controller rejection paths.",
)
text = text.replace(
"NEXT_PROMPT:\nVerify deployment checklist for issue #500 and post acceptance.",
"NEXT_PROMPT:\nImplement the missing tests for issue #500 and reopen the PR.",
)
return text
class TestControllerAcceptanceComment(unittest.TestCase):
def test_accepted_comment_valid(self):
result = issue_acceptance_gate.validate_controller_acceptance_comment(
_accepted_comment()
)
self.assertTrue(result["valid"], result["reasons"])
def test_missing_who_is_next_rejected(self):
text = _accepted_comment().replace("WHO_IS_NEXT:\ncontroller\n", "WHO_IS_NEXT:\n\n")
result = issue_acceptance_gate.validate_controller_acceptance_comment(text)
self.assertFalse(result["valid"])
self.assertTrue(any("WHO_IS_NEXT" in r for r in result["reasons"]))
def test_missing_next_prompt_on_rejection(self):
text = _rejection_comment()
text = text.replace(
"NEXT_PROMPT:\nImplement the missing tests for issue #500 and reopen the PR.\n",
"NEXT_PROMPT:\n\n",
)
result = issue_acceptance_gate.validate_controller_acceptance_comment(text)
self.assertFalse(result["valid"])
self.assertTrue(any("NEXT_PROMPT" in r for r in result["reasons"]))
def test_vague_merge_only_completion_detected(self):
text = "PR merged. Issue complete."
self.assertTrue(issue_acceptance_gate.claims_merge_only_issue_complete(text))
def test_rejection_paths_require_missing_work(self):
for state in (
"more-work-required",
"needs-tests",
"needs-docs",
"needs-feature-enhancement",
"needs-follow-up-issue",
):
text = _rejection_comment(state=state).replace(
"MISSING_WORK:\nAdd regression tests for controller rejection paths.\n",
"MISSING_WORK:\n\n",
)
result = issue_acceptance_gate.validate_controller_acceptance_comment(text)
self.assertFalse(result["valid"], state)
self.assertTrue(any("MISSING_WORK" in r for r in result["reasons"]))
def test_accepted_requires_checked_criteria(self):
text = _accepted_comment().replace(
"ACCEPTANCE_CRITERIA_CHECKED:\n- [x] documentation added\n- [x] validator tests added\n",
"ACCEPTANCE_CRITERIA_CHECKED:\n- [ ] documentation added\n",
)
result = issue_acceptance_gate.validate_controller_acceptance_comment(text)
self.assertFalse(result["valid"])
self.assertTrue(any("checked acceptance criterion" in r for r in result["reasons"]))
class TestFinalReportIntegration(unittest.TestCase):
def test_merge_only_complete_blocks_work_issue_report(self):
report = (
"## Controller Handoff\n\n"
"- Task: work issue #500\n"
"- Merge result: merged\n"
"- Current status: PR merged; issue complete\n"
)
result = assess_final_report_validator(report, "work_issue")
self.assertTrue(
any(
f["rule_id"] == "shared.issue_acceptance_gate"
for f in result["findings"]
),
result["findings"],
)
def test_pending_acceptance_allowed(self):
report = (
"## Controller Handoff\n\n"
"- Task: work issue #500\n"
"- Merge result: merged\n"
"- Current status: controller acceptance pending\n"
)
result = assess_final_report_validator(report, "work_issue")
self.assertFalse(
any(
f["rule_id"] == "shared.issue_acceptance_gate"
for f in result["findings"]
),
result["findings"],
)
def test_accepted_block_allows_complete_claim(self):
report = _accepted_comment() + "\n\nIssue is complete."
gate = issue_acceptance_gate.validate_final_report_issue_acceptance(report)
self.assertTrue(gate["valid"], gate["reasons"])
if __name__ == "__main__":
unittest.main()