Files
Gitea-Tools/tests/test_root_checkout_guard.py
T
sysadminandClaude Opus 4.8 be592d6d4d feat: add root checkout guard for contaminated control checkout (Closes #475)
Introduce root_checkout_guard helper and enforce it in verify_preflight_purity
so author, reviewer, and merger mutations fail closed when the stable control
checkout is on the wrong branch, detached, dirty, or diverged from prgs/master.
Isolated branches/... worktrees and reconciler paths remain allowed.

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

161 lines
5.9 KiB
Python

"""Tests for root checkout guard (#475)."""
from __future__ import annotations
import os
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import gitea_mcp_server as srv # noqa: E402
import root_checkout_guard as rcg # noqa: E402
CONTROL_ROOT = str(Path(__file__).resolve().parents[3])
BRANCHES_WORKTREE = str(Path(__file__).resolve().parents[1])
MASTER_SHA = "a" * 40
OTHER_SHA = "b" * 40
class TestAssessRootCheckoutGuard(unittest.TestCase):
def _assess(self, **kwargs):
defaults = {
"workspace_path": CONTROL_ROOT,
"canonical_repo_root": CONTROL_ROOT,
"current_branch": "master",
"head_sha": MASTER_SHA,
"porcelain_status": "",
"remote_master_sha": MASTER_SHA,
"resolved_role": "author",
}
defaults.update(kwargs)
return rcg.assess_root_checkout_guard(**defaults)
def test_clean_master_control_checkout_allowed(self):
result = self._assess()
self.assertTrue(result["proven"])
self.assertFalse(result["block"])
def test_branches_worktree_allowed_for_author(self):
result = self._assess(
workspace_path=BRANCHES_WORKTREE,
current_branch="feat/issue-475-root-checkout-guard",
head_sha=OTHER_SHA,
resolved_role="author",
)
self.assertTrue(result["proven"])
def test_branches_worktree_allowed_for_reviewer(self):
result = self._assess(
workspace_path=f"{CONTROL_ROOT}/branches/review-pr-1",
current_branch="review-pr-1",
resolved_role="reviewer",
)
self.assertTrue(result["proven"])
def test_reconciler_always_allowed(self):
result = self._assess(
current_branch="feat/some-branch",
head_sha=OTHER_SHA,
porcelain_status=" M gitea_mcp_server.py\n",
resolved_role="reconciler",
)
self.assertTrue(result["proven"])
def test_feature_branch_on_control_checkout_blocked(self):
result = self._assess(
current_branch="feat/issue-99-example",
head_sha=OTHER_SHA,
)
self.assertTrue(result["block"])
self.assertIn("not a stable base branch", result["reasons"][0])
def test_detached_head_blocked(self):
result = self._assess(current_branch=None)
self.assertTrue(result["block"])
self.assertIn("detached HEAD", result["reasons"][0])
def test_dirty_control_checkout_blocked(self):
result = self._assess(porcelain_status=" M gitea_mcp_server.py\n")
self.assertTrue(result["block"])
self.assertIn("tracked local edits", result["reasons"][0])
def test_head_behind_prgs_master_blocked(self):
result = self._assess(
head_sha=OTHER_SHA,
remote_master_sha=MASTER_SHA,
)
self.assertTrue(result["block"])
self.assertIn("does not match prgs/master", result["reasons"][0])
def test_merger_requires_clean_control_checkout(self):
result = self._assess(
workspace_path=BRANCHES_WORKTREE,
current_branch="feat/issue-475-root-checkout-guard",
head_sha=OTHER_SHA,
resolved_role="merger",
)
self.assertTrue(result["block"])
class TestVerifyPreflightRootGuardIntegration(unittest.TestCase):
def setUp(self):
srv._preflight_whoami_called = True
srv._preflight_capability_called = True
srv._preflight_resolved_role = "reviewer"
srv._preflight_whoami_violation = False
srv._preflight_capability_violation = False
self._orig_in_test = srv._preflight_in_test_mode
srv._preflight_in_test_mode = lambda: False
def tearDown(self):
srv._preflight_in_test_mode = self._orig_in_test
@patch("gitea_mcp_server._get_workspace_porcelain", return_value="")
@patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value=MASTER_SHA)
@patch("gitea_mcp_server.issue_lock_worktree.read_worktree_git_state")
@patch("gitea_mcp_server._resolve_author_mutation_context")
def test_reviewer_from_contaminated_root_blocked(
self, mock_ctx, mock_git, _remote_sha, _porcelain,
):
srv._preflight_capability_baseline_porcelain = ""
mock_ctx.return_value = {
"workspace_path": CONTROL_ROOT,
"canonical_repo_root": CONTROL_ROOT,
"process_project_root": CONTROL_ROOT,
}
mock_git.return_value = {
"current_branch": "feat/hijacked-root",
"head_sha": OTHER_SHA,
"porcelain_status": "",
}
with self.assertRaises(RuntimeError) as ctx:
srv.verify_preflight_purity("prgs", worktree_path=CONTROL_ROOT)
self.assertIn("Root checkout guard (#475)", str(ctx.exception))
self.assertIn(rcg.REMEDIATION, str(ctx.exception))
@patch("gitea_mcp_server._get_workspace_porcelain", return_value="")
@patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value=MASTER_SHA)
@patch("gitea_mcp_server.issue_lock_worktree.read_worktree_git_state")
@patch("gitea_mcp_server._resolve_author_mutation_context")
def test_reviewer_from_branches_worktree_allowed(
self, mock_ctx, mock_git, _remote_sha, _porcelain,
):
srv._preflight_capability_baseline_porcelain = ""
mock_ctx.return_value = {
"workspace_path": BRANCHES_WORKTREE,
"canonical_repo_root": CONTROL_ROOT,
"process_project_root": BRANCHES_WORKTREE,
}
mock_git.return_value = {
"current_branch": "feat/issue-475-root-checkout-guard",
"head_sha": OTHER_SHA,
"porcelain_status": "",
}
srv.verify_preflight_purity("prgs", worktree_path=BRANCHES_WORKTREE)
if __name__ == "__main__":
unittest.main()