"""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"])) def test_branches_worktree_as_project_root_passes(self): """MCP launched from a branches/ worktree uses that path as PROJECT_ROOT.""" worktree_root = f"{self.ROOT}/branches/issue-274" result = amw.assess_author_mutation_worktree( workspace_path=worktree_root, project_root=worktree_root, current_branch="feat/issue-274-branches-only-worktrees", ) self.assertTrue(result["proven"]) self.assertFalse(result["block"]) 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" mcp_server._preflight_resolved_task = None control_root = "/repo/Gitea-Tools" with mock.patch.object(mcp_server, "PROJECT_ROOT", control_root): with mock.patch.object(mcp_server, "_enforce_root_checkout_guard"): with mock.patch( "gitea_mcp_server._session_author_lock_worktree", return_value=None, ): with mock.patch( "gitea_auth.get_profile", return_value={"profile_name": "gitea-author"}, ): with mock.patch.dict( "os.environ", {"GITEA_TEST_PORCELAIN": ""}, clear=False, ): with self.assertRaises(RuntimeError) as ctx: mcp_server.verify_preflight_purity() blob = str(ctx.exception) self.assertTrue( "Branches-only mutation guard" in blob or "control checkout" in blob or "author worktree" in blob.lower() or "#618" in blob, msg=blob, ) 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" mcp_server._preflight_resolved_task = None worktree = "/repo/Gitea-Tools/branches/issue-274" healthy_ctx = { "workspace_path": worktree, "workspace_binding_source": "worktree_path argument", "workspace_role_kind": "author", "ignored_bindings": [], "process_project_root": "/repo/Gitea-Tools", "canonical_repo_root": "/repo/Gitea-Tools", "roots_aligned": True, "bound_worktree_missing": False, "author_worktree_block": False, "author_worktree_reasons": [], "author_worktree_resolution": { "proven": True, "block": False, "bound_worktree_missing": False, "workspace_path": worktree, "workspace_binding_source": "worktree_path argument", "reasons": [], }, "path_exists": True, "in_git_worktree_list": True, "inspected_git_root": worktree, } with mock.patch.object(mcp_server, "_enforce_root_checkout_guard"): with mock.patch.object( mcp_server, "_session_author_lock_worktree", return_value=None ): with mock.patch.object( mcp_server, "_resolve_namespace_mutation_context", return_value=healthy_ctx, ): with mock.patch.dict( "os.environ", {"GITEA_TEST_PORCELAIN": ""}, clear=False, ): mcp_server.verify_preflight_purity(worktree_path=worktree) if __name__ == "__main__": unittest.main()