gitea_lock_issue and gitea_create_pr accept worktree_path so lock preconditions validate the caller's scratch clone instead of the shared MCP server CWD. Lock records store the validated path; PR creation fails closed when the declared path does not match. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
99 lines
3.4 KiB
Python
99 lines
3.4 KiB
Python
"""Tests for issue-lock worktree validation (#249)."""
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))
|
|
|
|
import issue_lock_worktree # noqa: E402
|
|
|
|
|
|
class TestIssueLockWorktreeAssessment(unittest.TestCase):
|
|
def test_clean_base_branch_passes(self):
|
|
result = issue_lock_worktree.assess_issue_lock_worktree(
|
|
worktree_path="/scratch/wt",
|
|
current_branch="master",
|
|
porcelain_status="",
|
|
)
|
|
self.assertTrue(result["proven"])
|
|
self.assertFalse(result["block"])
|
|
|
|
def test_dirty_tracked_files_fail(self):
|
|
result = issue_lock_worktree.assess_issue_lock_worktree(
|
|
worktree_path="/scratch/wt",
|
|
current_branch="master",
|
|
porcelain_status=" M gitea_mcp_server.py\n",
|
|
)
|
|
self.assertFalse(result["proven"])
|
|
self.assertIn("tracked file edits exist before issue lock", result["reasons"][0])
|
|
|
|
def test_feature_branch_fails(self):
|
|
result = issue_lock_worktree.assess_issue_lock_worktree(
|
|
worktree_path="/scratch/wt",
|
|
current_branch="feat/issue-243-forbidden-git-gaps",
|
|
porcelain_status="",
|
|
)
|
|
self.assertFalse(result["proven"])
|
|
self.assertIn("issue lock must be taken from base branch", result["reasons"][0])
|
|
|
|
def test_untracked_files_do_not_block(self):
|
|
result = issue_lock_worktree.assess_issue_lock_worktree(
|
|
worktree_path="/scratch/wt",
|
|
current_branch="main",
|
|
porcelain_status="?? notes.txt\n",
|
|
)
|
|
self.assertTrue(result["proven"])
|
|
|
|
|
|
class TestIssueLockWorktreeResolution(unittest.TestCase):
|
|
def test_explicit_path_wins(self):
|
|
resolved = issue_lock_worktree.resolve_author_worktree_path(
|
|
"/tmp/scratch/wt", "/shared/dev"
|
|
)
|
|
self.assertEqual(resolved, os.path.realpath("/tmp/scratch/wt"))
|
|
|
|
def test_env_var_when_explicit_missing(self):
|
|
with patch.dict(os.environ, {"GITEA_AUTHOR_WORKTREE": "/tmp/from-env"}):
|
|
resolved = issue_lock_worktree.resolve_author_worktree_path(
|
|
None, "/shared/dev"
|
|
)
|
|
self.assertEqual(resolved, os.path.realpath("/tmp/from-env"))
|
|
|
|
def test_project_root_fallback(self):
|
|
resolved = issue_lock_worktree.resolve_author_worktree_path(
|
|
None, "/shared/dev"
|
|
)
|
|
self.assertEqual(resolved, os.path.realpath("/shared/dev"))
|
|
|
|
|
|
class TestPrWorktreeMatch(unittest.TestCase):
|
|
def test_matching_paths_pass(self):
|
|
result = issue_lock_worktree.verify_pr_worktree_matches_lock(
|
|
"/tmp/scratch/wt",
|
|
"/tmp/scratch/wt",
|
|
"/shared/dev",
|
|
)
|
|
self.assertTrue(result["proven"])
|
|
|
|
def test_mismatch_fails(self):
|
|
result = issue_lock_worktree.verify_pr_worktree_matches_lock(
|
|
"/tmp/scratch/wt",
|
|
"/shared/dev",
|
|
"/shared/dev",
|
|
)
|
|
self.assertFalse(result["proven"])
|
|
self.assertIn("does not match locked worktree", result["reasons"][0])
|
|
|
|
def test_missing_locked_path_skips_check(self):
|
|
result = issue_lock_worktree.verify_pr_worktree_matches_lock(
|
|
None,
|
|
"/any/path",
|
|
"/shared/dev",
|
|
)
|
|
self.assertTrue(result["proven"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main() |