"""Unit tests for stacked-PR base policy (#484).""" import unittest import stacked_pr_support as sps def _pr(number, branch, state="open"): return {"number": number, "state": state, "head": {"ref": branch}} OPEN_PRS = [ _pr(479, "feat/issue-478-mcp-menu-shell"), _pr(481, "feat/issue-477-lock-adoption-proof"), ] # Motivating case (#482 stacked on #479 / #478). STACKED_BODY = ( "Closes #482.\n\n" "Stacked on PR #479 / issue #478.\n" "Base branch: feat/issue-478-mcp-menu-shell\n" "Head branch: feat/issue-482-skip-stale-request-changes-pr\n" "Do not merge before PR #479 lands." ) class TestIsBaseBranch(unittest.TestCase): def test_master_main_dev_are_base(self): for b in ("master", "main", "dev"): self.assertTrue(sps.is_base_branch(b)) def test_feature_branch_is_not_base(self): self.assertFalse(sps.is_base_branch("feat/issue-478-mcp-menu-shell")) class TestStackedBaseDeclaration(unittest.TestCase): def test_no_declaration_is_normal_path(self): out = sps.assess_stacked_base_declaration( stacked_base_branch=None, stacked_base_pr=None, open_prs=OPEN_PRS ) self.assertFalse(out["block"]) self.assertFalse(out["declared"]) self.assertIsNone(out["approved"]) def test_valid_open_pr_base_is_approved(self): out = sps.assess_stacked_base_declaration( stacked_base_branch="feat/issue-478-mcp-menu-shell", stacked_base_pr=479, open_prs=OPEN_PRS, ) self.assertFalse(out["block"]) self.assertEqual(out["approved"]["branch"], "feat/issue-478-mcp-menu-shell") self.assertEqual(out["approved"]["pr_number"], 479) self.assertTrue(out["approved"]["verified_open"]) def test_missing_pr_number_blocks(self): out = sps.assess_stacked_base_declaration( stacked_base_branch="feat/issue-478-mcp-menu-shell", stacked_base_pr=None, open_prs=OPEN_PRS, ) self.assertTrue(out["block"]) self.assertIn("without stacked_base_pr", out["reasons"][0]) def test_arbitrary_branch_with_no_open_pr_blocks(self): out = sps.assess_stacked_base_declaration( stacked_base_branch="feat/random-unrelated-branch", stacked_base_pr=999, open_prs=OPEN_PRS, ) self.assertTrue(out["block"]) self.assertIn("does not correspond to any OPEN pull request", out["reasons"][0]) def test_stale_merged_base_blocks(self): merged = [_pr(479, "feat/issue-478-mcp-menu-shell", state="closed")] out = sps.assess_stacked_base_declaration( stacked_base_branch="feat/issue-478-mcp-menu-shell", stacked_base_pr=479, open_prs=merged, ) self.assertTrue(out["block"]) self.assertIsNone(out["approved"]) def test_pr_number_mismatch_blocks(self): out = sps.assess_stacked_base_declaration( stacked_base_branch="feat/issue-478-mcp-menu-shell", stacked_base_pr=481, # wrong PR for this branch open_prs=OPEN_PRS, ) self.assertTrue(out["block"]) self.assertIn("does not match the open", out["reasons"][0]) def test_declaring_a_base_branch_blocks(self): out = sps.assess_stacked_base_declaration( stacked_base_branch="master", stacked_base_pr=1, open_prs=OPEN_PRS ) self.assertTrue(out["block"]) self.assertIn("already a normal base branch", out["reasons"][0]) class TestStackedPrBody(unittest.TestCase): def test_complete_body_has_no_missing_fields(self): missing = sps.assess_stacked_pr_body( STACKED_BODY, base_branch="feat/issue-478-mcp-menu-shell", pr_number=479 ) self.assertEqual(missing, []) def test_missing_all_fields(self): missing = sps.assess_stacked_pr_body( "just some text", base_branch="feat/issue-478-mcp-menu-shell", pr_number=479 ) self.assertEqual(len(missing), 3) def test_missing_merge_ordering_only(self): body = "Base branch feat/issue-478-mcp-menu-shell for PR #479" missing = sps.assess_stacked_pr_body( body, base_branch="feat/issue-478-mcp-menu-shell", pr_number=479 ) self.assertEqual(len(missing), 1) self.assertIn("merge-ordering", missing[0]) class TestCreatePrBase(unittest.TestCase): APPROVED = {"branch": "feat/issue-478-mcp-menu-shell", "pr_number": 479, "verified_open": True} def test_master_base_passes_without_stacked_metadata(self): out = sps.assess_create_pr_base( base="master", approved_stacked_base=None, body="Closes #1", open_prs=[] ) self.assertFalse(out["block"]) self.assertFalse(out["stacked"]) def test_non_base_without_approval_blocks(self): out = sps.assess_create_pr_base( base="feat/issue-478-mcp-menu-shell", approved_stacked_base=None, body=STACKED_BODY, open_prs=OPEN_PRS, ) self.assertTrue(out["block"]) self.assertIn("no approved stacked base", out["reasons"][0]) def test_non_base_mismatched_approval_blocks(self): out = sps.assess_create_pr_base( base="feat/some-other-branch", approved_stacked_base=self.APPROVED, body=STACKED_BODY, open_prs=OPEN_PRS, ) self.assertTrue(out["block"]) self.assertIn("does not match the issue lock's approved stacked base", out["reasons"][0]) def test_approved_base_with_good_body_passes(self): out = sps.assess_create_pr_base( base="feat/issue-478-mcp-menu-shell", approved_stacked_base=self.APPROVED, body=STACKED_BODY, open_prs=OPEN_PRS, ) self.assertFalse(out["block"]) self.assertTrue(out["stacked"]) self.assertEqual(out["stacked_base_pr"], 479) def test_approved_base_now_stale_blocks(self): out = sps.assess_create_pr_base( base="feat/issue-478-mcp-menu-shell", approved_stacked_base=self.APPROVED, body=STACKED_BODY, open_prs=[_pr(479, "feat/issue-478-mcp-menu-shell", state="merged")], ) self.assertTrue(out["block"]) self.assertIn("no longer corresponds to an OPEN", out["reasons"][0]) def test_approved_base_with_incomplete_body_blocks(self): out = sps.assess_create_pr_base( base="feat/issue-478-mcp-menu-shell", approved_stacked_base=self.APPROVED, body="Closes #482 only", open_prs=OPEN_PRS, ) self.assertTrue(out["block"]) self.assertIn("must document the stack", out["reasons"][0]) if __name__ == "__main__": unittest.main()