@@ -1755,6 +1755,86 @@ def test_workflow_sanitizes_pr_body_closing_tag(self):
17551755 assert "</previous-ai-review-output>" in text
17561756
17571757
1758+ class TestWorkflowCommentPosting :
1759+ """The workflow has TWO rerun-detection gates that must agree:
1760+ 1. YAML `IS_RERUN` env in the prompt-build step — controls whether
1761+ the prompt includes the <previous-ai-review-output> block and
1762+ re-review framing.
1763+ 2. JS `isRerun` in the post-comment step — controls whether the
1764+ comment is created fresh or updates the canonical comment.
1765+
1766+ If they disagree, you get nonsense states like "new comment posted but
1767+ prompt didn't see prior review" (synchronize bug pre-fix) or "canonical
1768+ comment updated but prompt was framed as rerun" (the inverse).
1769+
1770+ The contract: `pull_request.opened` is non-rerun; everything else
1771+ (`pull_request.synchronize`, `pull_request.reopened`, `issue_comment`,
1772+ `pull_request_review_comment`) is a rerun."""
1773+
1774+ @pytest .fixture
1775+ def workflow_text (self ):
1776+ assert _SCRIPT_PATH is not None
1777+ repo_root = _SCRIPT_PATH .parent .parent .parent
1778+ wf = repo_root / ".github" / "workflows" / "ai_pr_review.yml"
1779+ if not wf .exists ():
1780+ pytest .skip ("workflow not found" )
1781+ return wf .read_text ()
1782+
1783+ # --- Post-comment JS gate ---
1784+
1785+ def test_js_isrerun_includes_non_opened_pull_request (self , workflow_text ):
1786+ """JS gate: non-opened pull_request events create a new comment."""
1787+ assert 'context.payload.action !== "opened"' in workflow_text , (
1788+ "post-comment isRerun must treat pull_request events other than "
1789+ "'opened' as reruns; otherwise synchronize/reopened overwrite the "
1790+ "canonical review comment and lose prior content."
1791+ )
1792+
1793+ def test_js_isrerun_still_includes_comment_events (self , workflow_text ):
1794+ assert 'context.eventName === "issue_comment"' in workflow_text
1795+ assert 'context.eventName === "pull_request_review_comment"' in workflow_text
1796+
1797+ # --- YAML IS_RERUN gate ---
1798+
1799+ def test_yaml_isrerun_includes_non_opened_pull_request (self , workflow_text ):
1800+ """YAML gate: non-opened pull_request events make the prompt include
1801+ the previous-review block. Must agree with the JS gate above."""
1802+ assert (
1803+ "github.event_name == 'pull_request' && github.event.action != 'opened'"
1804+ in workflow_text
1805+ ), (
1806+ "prompt-build IS_RERUN must include non-opened pull_request events "
1807+ "alongside comment triggers; otherwise synchronize/reopened pushes "
1808+ "create a new comment but the prompt omits <previous-ai-review-output>."
1809+ )
1810+
1811+ def test_yaml_isrerun_still_includes_comment_events (self , workflow_text ):
1812+ assert "github.event_name == 'issue_comment'" in workflow_text
1813+ assert "github.event_name == 'pull_request_review_comment'" in workflow_text
1814+
1815+ # --- Parity contract: both gates must enumerate the same trigger set ---
1816+
1817+ def test_both_gates_enumerate_same_triggers (self , workflow_text ):
1818+ """Whatever the gates use to express the rerun set, both must mention
1819+ each of the four rerun-trigger names so they cannot silently disagree.
1820+
1821+ This is a string-presence check (not a true semantic equality), but it
1822+ catches the realistic regression: someone editing one gate and
1823+ forgetting the other."""
1824+ rerun_signals = [
1825+ "issue_comment" ,
1826+ "pull_request_review_comment" ,
1827+ # The synchronize/reopened branch is expressed via action != opened
1828+ # in both gates, so we anchor on the action-comparison strings:
1829+ "github.event.action != 'opened'" , # YAML
1830+ 'context.payload.action !== "opened"' , # JS
1831+ ]
1832+ for signal in rerun_signals :
1833+ assert signal in workflow_text , (
1834+ f"Expected rerun-set signal { signal !r} not found in workflow YAML"
1835+ )
1836+
1837+
17581838class TestExtractResponseText :
17591839 def test_prefers_output_text_field (self , review_mod ):
17601840 result = {"output_text" : "Direct text." , "output" : []}
0 commit comments