173 lines
6.4 KiB
Python
173 lines
6.4 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
|
|
|
|
current_file_path = Path(__file__).resolve()
|
|
if "branches" in current_file_path.parts:
|
|
CONTROL_ROOT = str(current_file_path.parents[3])
|
|
BRANCHES_WORKTREE = str(current_file_path.parents[1])
|
|
else:
|
|
CONTROL_ROOT = str(current_file_path.parents[1])
|
|
BRANCHES_WORKTREE = str(current_file_path.parents[1] / "branches" / "mock-worktree")
|
|
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("os.path.isdir", return_value=True)
|
|
@patch("os.path.exists", return_value=True)
|
|
@patch("subprocess.run")
|
|
@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, mock_run, _exists, _isdir,
|
|
):
|
|
import unittest.mock
|
|
mock_run.return_value = unittest.mock.MagicMock(
|
|
returncode=0,
|
|
stdout=f"{CONTROL_ROOT}/.git\n",
|
|
)
|
|
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() |