feat: enforce branches-only author mutation worktree guard (Closes #274)
Add author_mutation_worktree assessment and integrate it into MCP preflight so author mutations fail closed outside branches/ session worktrees, with tests for control-checkout and branches-path cases. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""Tests for branches-only author mutation worktree guard (#274)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
import author_mutation_worktree as amw # noqa: E402
|
||||
|
||||
|
||||
class TestPathUnderBranches(unittest.TestCase):
|
||||
ROOT = "/repo/Gitea-Tools"
|
||||
|
||||
def test_branches_subdirectory_passes(self):
|
||||
self.assertTrue(
|
||||
amw.is_path_under_branches(f"{self.ROOT}/branches/issue-274", self.ROOT)
|
||||
)
|
||||
|
||||
def test_control_checkout_fails(self):
|
||||
self.assertFalse(amw.is_path_under_branches(self.ROOT, self.ROOT))
|
||||
|
||||
def test_sibling_directory_fails(self):
|
||||
self.assertFalse(
|
||||
amw.is_path_under_branches("/repo/other-checkout", self.ROOT)
|
||||
)
|
||||
|
||||
|
||||
class TestAssessAuthorMutationWorktree(unittest.TestCase):
|
||||
ROOT = "/repo/Gitea-Tools"
|
||||
|
||||
def test_branches_worktree_passes(self):
|
||||
result = amw.assess_author_mutation_worktree(
|
||||
workspace_path=f"{self.ROOT}/branches/issue-274",
|
||||
project_root=self.ROOT,
|
||||
current_branch="feat/issue-274-branches-only-worktrees",
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
self.assertFalse(result["block"])
|
||||
|
||||
def test_control_checkout_blocks(self):
|
||||
result = amw.assess_author_mutation_worktree(
|
||||
workspace_path=self.ROOT,
|
||||
project_root=self.ROOT,
|
||||
current_branch="master",
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(result["block"])
|
||||
self.assertIn("control checkout", result["reasons"][0])
|
||||
|
||||
def test_drifted_control_branch_blocks(self):
|
||||
result = amw.assess_author_mutation_worktree(
|
||||
workspace_path=self.ROOT,
|
||||
project_root=self.ROOT,
|
||||
current_branch="feat/some-task",
|
||||
)
|
||||
self.assertTrue(result["block"])
|
||||
self.assertTrue(any("drift" in r for r in result["reasons"]))
|
||||
|
||||
|
||||
class TestPreflightIntegration(unittest.TestCase):
|
||||
def test_verify_preflight_blocks_control_checkout_with_test_porcelain(self):
|
||||
import mcp_server
|
||||
|
||||
mcp_server._preflight_whoami_called = True
|
||||
mcp_server._preflight_capability_called = True
|
||||
mcp_server._preflight_resolved_role = "author"
|
||||
with mock.patch.dict(
|
||||
"os.environ",
|
||||
{"GITEA_TEST_PORCELAIN": ""},
|
||||
clear=False,
|
||||
):
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
mcp_server.verify_preflight_purity()
|
||||
self.assertIn("Branches-only mutation guard", str(ctx.exception))
|
||||
|
||||
def test_verify_preflight_allows_branches_worktree(self):
|
||||
import mcp_server
|
||||
|
||||
mcp_server._preflight_whoami_called = True
|
||||
mcp_server._preflight_capability_called = True
|
||||
mcp_server._preflight_resolved_role = "author"
|
||||
worktree = "/repo/Gitea-Tools/branches/issue-274"
|
||||
with mock.patch.dict(
|
||||
"os.environ",
|
||||
{"GITEA_TEST_PORCELAIN": ""},
|
||||
clear=False,
|
||||
):
|
||||
mcp_server.verify_preflight_purity(worktree_path=worktree)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user