Files
Gitea-Tools/tests/test_canonical_state_comments.py
sysadminandClaude Opus 4.8 9235f3a23c feat: require canonical next-action state comments (#495)
Add canonical issue/PR/discussion state comment templates and validators,
final-report rules for STATE/WHO_IS_NEXT/NEXT_ACTION/NEXT_PROMPT, and
queue-controller guidance. Posting enforcement remains in #496.

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

144 lines
3.9 KiB
Python

"""Tests for canonical state comment validation (#495)."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from canonical_state_comments import ( # noqa: E402
validate_canonical_state_comment,
validate_final_report_state_update,
)
GOOD_ISSUE_STATE = """## Canonical Issue State
STATE:
ready-for-author
WHO_IS_NEXT:
author
NEXT_ACTION:
Implement the focused validator and open a PR.
NEXT_PROMPT:
Find issue #495, load the work-issue workflow, implement the validator, and open a PR.
WHAT_HAPPENED:
Controller selected the canonical tracking issue.
WHY:
The duplicate issue points here as canonical.
RELATED_PRS:
none
BRANCH:
none
HEAD_SHA:
none
VALIDATION:
none
BLOCKERS:
none
"""
class TestCanonicalStateComments(unittest.TestCase):
def test_good_issue_state_validates(self):
result = validate_canonical_state_comment(GOOD_ISSUE_STATE)
self.assertTrue(result["valid"])
self.assertEqual(result["reasons"], [])
def test_missing_next_actor_is_rejected(self):
result = validate_canonical_state_comment(
GOOD_ISSUE_STATE.replace("WHO_IS_NEXT:\nauthor", "WHO_IS_NEXT:\n")
)
self.assertFalse(result["valid"])
self.assertIn("WHO_IS_NEXT", " ".join(result["reasons"]))
def test_missing_next_prompt_is_rejected(self):
result = validate_canonical_state_comment(
GOOD_ISSUE_STATE.replace(
"NEXT_PROMPT:\nFind issue #495, load the work-issue workflow, implement the validator, and open a PR.",
"NEXT_PROMPT:\n",
)
)
self.assertFalse(result["valid"])
self.assertIn("NEXT_PROMPT", " ".join(result["reasons"]))
def test_vague_next_action_is_rejected(self):
result = validate_canonical_state_comment(
GOOD_ISSUE_STATE.replace(
"NEXT_ACTION:\nImplement the focused validator and open a PR.",
"NEXT_ACTION:\ncontinue",
)
)
self.assertFalse(result["valid"])
self.assertIn("too vague", " ".join(result["reasons"]))
def test_ready_to_merge_requires_approval_and_head_sha(self):
text = """## Canonical PR State
STATE:
ready-to-merge
WHO_IS_NEXT:
merger
NEXT_ACTION:
Merge PR #12 after checking the current head.
NEXT_PROMPT:
Load the merge workflow and merge PR #12 if gates pass.
ISSUE:
#11
HEAD_SHA:
abc123
REVIEW_STATUS:
none
MERGE_READY:
yes
"""
result = validate_canonical_state_comment(text)
self.assertFalse(result["valid"])
reasons = " ".join(result["reasons"])
self.assertIn("approved REVIEW_STATUS", reasons)
self.assertIn("full HEAD_SHA", reasons)
def test_superseded_requires_canonical_item(self):
text = GOOD_ISSUE_STATE.replace("STATE:\nready-for-author", "STATE:\nsuperseded")
result = validate_canonical_state_comment(text)
self.assertFalse(result["valid"])
self.assertIn("canonical item", " ".join(result["reasons"]))
def test_blocked_requires_unblock_condition(self):
text = GOOD_ISSUE_STATE.replace("STATE:\nready-for-author", "STATE:\nblocked")
text = text.replace("BLOCKERS:\nnone", "BLOCKERS:\n")
result = validate_canonical_state_comment(text)
self.assertFalse(result["valid"])
self.assertIn("BLOCKERS", " ".join(result["reasons"]))
def test_final_report_claim_without_block_is_rejected(self):
report = "Posted canonical state comment on issue #495."
result = validate_final_report_state_update(report)
self.assertTrue(result["applicable"])
self.assertFalse(result["valid"])
def test_final_report_without_state_claim_not_applicable(self):
result = validate_final_report_state_update("ordinary final report")
self.assertFalse(result["applicable"])
self.assertTrue(result["valid"])
if __name__ == "__main__":
unittest.main()