@@ -345,6 +345,24 @@ def test_mark_pr_opened_flags_the_pr_for_review(self, mock_run):
345345 calls = [call .args [0 ] for call in mock_run .call_args_list ]
346346 self .assertIn (["gh" , "pr" , "edit" , "4742" , "--repo" , "springfall2008/batpred" , "--add-label" , "BOT_REVIEW" ], calls )
347347
348+ @patch ("triage_daemon.subprocess.run" )
349+ def test_mark_pr_opened_with_cleanup_flags_review_and_cleanup_together (self , mock_run ):
350+ """A PR this run just opened gets BOT_REVIEW and BOT_CLEANUP in one edit, so the
351+ review's findings are acted on without anyone relabelling it by hand."""
352+ mock_run .return_value = MagicMock (stdout = json .dumps ([{"number" : 4742 }]))
353+ triage_daemon .mark_pr_opened (4720 , cleanup = True )
354+ calls = [call .args [0 ] for call in mock_run .call_args_list ]
355+ self .assertIn (["gh" , "pr" , "edit" , "4742" , "--repo" , "springfall2008/batpred" , "--add-label" , "BOT_REVIEW" , "--add-label" , "BOT_CLEANUP" ], calls )
356+
357+ @patch ("triage_daemon.subprocess.run" )
358+ def test_mark_pr_opened_without_cleanup_never_adds_bot_cleanup (self , mock_run ):
359+ """The default path is also taken for a PR found already open on entry, which may be
360+ a person's own branch - that must never gain a label that pushes commits to it."""
361+ mock_run .return_value = MagicMock (stdout = json .dumps ([{"number" : 4742 }]))
362+ triage_daemon .mark_pr_opened (4720 )
363+ calls = [call .args [0 ] for call in mock_run .call_args_list ]
364+ self .assertFalse (any ("BOT_CLEANUP" in call for call in calls ))
365+
348366 @patch ("triage_daemon.subprocess.run" )
349367 def test_mark_pr_opened_skips_flagging_when_no_pr_found (self , mock_run ):
350368 """Defensive path: an empty PR search (e.g. a race with the PR being closed
@@ -387,6 +405,15 @@ def test_adds_bot_review_to_the_pr_scoped_to_repo(self, mock_run):
387405 args = mock_run .call_args [0 ][0 ]
388406 self .assertEqual (args , ["gh" , "pr" , "edit" , "4742" , "--repo" , "springfall2008/batpred" , "--add-label" , "BOT_REVIEW" ])
389407
408+ @patch ("triage_daemon.subprocess.run" )
409+ def test_cleanup_adds_bot_cleanup_in_the_same_edit (self , mock_run ):
410+ """Both labels land in one gh call, so no poll cycle can see BOT_CLEANUP without
411+ BOT_REVIEW and run the cleanup before the review it is meant to act on."""
412+ triage_daemon .flag_pr_for_review (4742 , cleanup = True )
413+ mock_run .assert_called_once ()
414+ args = mock_run .call_args [0 ][0 ]
415+ self .assertEqual (args , ["gh" , "pr" , "edit" , "4742" , "--repo" , "springfall2008/batpred" , "--add-label" , "BOT_REVIEW" , "--add-label" , "BOT_CLEANUP" ])
416+
390417
391418class PermissionModelTests (unittest .TestCase ):
392419 """Regression tests for the ALLOWED_TOOLS/DISALLOWED_TOOLS delta between the
@@ -1663,11 +1690,12 @@ def test_skips_triage_when_already_triaged(self):
16631690 self .patches ["create_pr" ].assert_called_once_with (4720 )
16641691
16651692 def test_marks_opened_when_a_pr_exists_afterwards (self ):
1666- """If a PR references the issue after create_pr() runs, swap to BOT_PR_OPENED."""
1693+ """If a PR references the issue after create_pr() runs, swap to BOT_PR_OPENED - and
1694+ since this run opened it, queue the cleanup that acts on its review as well."""
16671695 self .patches ["has_existing_pr" ].side_effect = [False , True ]
16681696 self .patches ["ensure_triaged" ].return_value = True
16691697 triage_daemon .process_bot_pr_issue ({"number" : 4720 , "labels" : [], "title" : "Solis TOU bit refused" })
1670- self .patches ["mark_pr_opened" ].assert_called_once_with (4720 )
1698+ self .patches ["mark_pr_opened" ].assert_called_once_with (4720 , cleanup = True )
16711699 self .patches ["mark_pr_failed" ].assert_not_called ()
16721700
16731701 def test_marks_failed_when_no_pr_exists_afterwards (self ):
@@ -1937,6 +1965,15 @@ def test_queries_open_prs_labelled_bot_cleanup(self, mock_run):
19371965 self .assertEqual (args [args .index ("--label" ) + 1 ], "BOT_CLEANUP" )
19381966 self .assertIn ("--limit" , args )
19391967
1968+ @patch ("triage_daemon.subprocess.run" )
1969+ def test_the_query_asks_for_labels (self , mock_run ):
1970+ """process_bot_cleanup_pr() holds a PR that still carries BOT_REVIEW, which it can
1971+ only see if the labels are fetched."""
1972+ mock_run .return_value = MagicMock (stdout = "[]" )
1973+ triage_daemon .fetch_bot_cleanup_prs ()
1974+ cmd = mock_run .call_args [0 ][0 ]
1975+ self .assertIn ("labels" , cmd [cmd .index ("--json" ) + 1 ].split ("," ))
1976+
19401977
19411978class RemovePrReviewLabelTests (unittest .TestCase ):
19421979 """Tests for remove_pr_review_label(), new - the BOT_REVIEW-on-PR flow."""
@@ -2372,6 +2409,21 @@ def test_failed_cleanup_marks_failed_instead_of_removing_the_label(self):
23722409 self .patches ["mark_pr_cleanup_failed" ].assert_called_once_with (4742 )
23732410 self .patches ["remove_pr_cleanup_label" ].assert_not_called ()
23742411
2412+ def test_waits_while_the_pr_still_carries_bot_review (self ):
2413+ """Cleanup acts on the review's findings, so with both labels set the review goes
2414+ first. Nothing runs and BOT_CLEANUP stays, so a later poll picks the PR up again
2415+ once the review has cleared BOT_REVIEW."""
2416+ triage_daemon .process_bot_cleanup_pr ({"number" : 4742 , "title" : "Add confirmed findings" , "labels" : [{"name" : "BOT_CLEANUP" }, {"name" : "BOT_REVIEW" }]})
2417+ self .patches ["sync_repo" ].assert_not_called ()
2418+ self .patches ["cleanup_pr" ].assert_not_called ()
2419+ self .patches ["remove_pr_cleanup_label" ].assert_not_called ()
2420+ self .patches ["mark_pr_cleanup_failed" ].assert_not_called ()
2421+
2422+ def test_runs_once_the_review_label_has_gone (self ):
2423+ """The hold is keyed on BOT_REVIEW alone - other labels do not block a cleanup."""
2424+ triage_daemon .process_bot_cleanup_pr ({"number" : 4742 , "title" : "Add confirmed findings" , "labels" : [{"name" : "BOT_CLEANUP" }, {"name" : "bug" }]})
2425+ self .patches ["cleanup_pr" ].assert_called_once_with (4742 )
2426+
23752427 @patch ("builtins.print" )
23762428 def test_prints_the_title_and_link_before_doing_anything (self , mock_print ):
23772429 """Prints the PR's title and an openable GitHub link as soon as work starts."""
0 commit comments