Files
Gitea-Tools/tests/test_lock_issue_mcp_registration.py
T
sysadmin dc899d23c8 fix(session): require config-backed mutation authority and workspace-bound targets (#714)
Close the #714 remediation gap left at 943d402 where env-only mutation
profiles, REMOTES Timesheet defaults treated as explicit caller input, and
machine-dependent Git remotes produced false greens.

- Fail closed when mutations lack a config-backed profile with non-empty
  allowed_repositories; env ops cannot invent mutation authority.
- Preserve omitted-vs-explicit org/repo provenance through the mutation
  gate; omitted targets bind to the verified workspace repository.
- Prefer workspace-aligned Git remotes over historical Timesheet defaults
  in MCP _resolve and CLI resolve_remote.
- Centralize deterministic config-backed mutation fixtures; migrate
  mutation tests off env-only authority without weakening security
  assertions.

Full suite: 2744 passed, 6 skipped.
2026-07-15 21:13:21 -04:00

133 lines
4.4 KiB
Python

from __future__ import annotations
import sys as _sys
from pathlib import Path as _Path
_sys.path.insert(0, str(_Path(__file__).resolve().parent))
from mutation_profile_fixture import shared_mutation_env # noqa: E402
import os
import tempfile
import unittest
from unittest.mock import patch
import issue_lock_store
import mcp_server
from mcp_server import gitea_create_pr, gitea_lock_issue
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
ISSUE_WRITE_ENV = shared_mutation_env(
"test-author-prgs",
)
CREATE_PR_ENV = shared_mutation_env(
"test-author-prgs",
GITEA_ALLOWED_OPERATIONS=(
"gitea.read,gitea.pr.create,gitea.branch.push,"
"gitea.issue.create,gitea.issue.close,gitea.issue.comment"
),
GITEA_FORBIDDEN_OPERATIONS=(
"gitea.pr.approve,gitea.pr.merge,gitea.pr.review"
),
)
def _clean_master_git_state_for_lock():
return {
"current_branch": "master",
"porcelain_status": "",
"base_equivalent": True,
"inspected_git_root": "/scratch/wt",
"base_branch": "origin/master",
}
def _registered_tool_names() -> set[str]:
manager = mcp_server.mcp._tool_manager
tools = getattr(manager, "_tools", None) or {}
return set(tools.keys())
class TestLockIssueMcpRegistration(unittest.TestCase):
def test_gitea_lock_issue_registered_as_public_mcp_tool(self):
names = _registered_tool_names()
self.assertIn("gitea_lock_issue", names)
def test_internal_list_open_pulls_not_exposed_as_mcp_tool(self):
names = _registered_tool_names()
self.assertNotIn("_list_open_pulls", names)
class TestCreatePrLockRegistrationFlow(unittest.TestCase):
def setUp(self):
self._lock_dir = tempfile.TemporaryDirectory()
self._env_patcher = patch.dict(
os.environ,
{
**CREATE_PR_ENV,
"GITEA_ISSUE_LOCK_DIR": self._lock_dir.name,
},
clear=True,
)
self._env_patcher.start()
self._dup_fetcher_patcher = patch(
"mcp_server.issue_duplicate_context_fetcher",
return_value=([], [], {"status": "not_claimed"}),
)
self._dup_fetcher_patcher.start()
def tearDown(self):
self._dup_fetcher_patcher.stop()
self._env_patcher.stop()
self._lock_dir.cleanup()
@patch("mcp_server.role_session_router.check_author_mutation_after_reviewer_stop",
return_value=(True, []))
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_create_pr_still_fails_closed_without_issue_lock(self, _auth, _role):
with self.assertRaises(RuntimeError) as ctx:
gitea_create_pr(
title="feat: X Closes #521",
head="feat/issue-521-lock-issue-registration",
remote="prgs",
)
self.assertIn("Issue lock is missing", str(ctx.exception))
@patch("mcp_server.api_request")
@patch(
"mcp_server.issue_lock_worktree.read_worktree_git_state",
return_value=_clean_master_git_state_for_lock(),
)
@patch("mcp_server.api_get_all", return_value=[{"id": 4, "name": "status:pr-open"}])
@patch("mcp_server.role_session_router.check_author_mutation_after_reviewer_stop",
return_value=(True, []))
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_create_pr_proceeds_after_valid_issue_lock(
self, _auth, _role, _api, _git_state, mock_api_request
):
worktree = os.path.realpath(os.getcwd())
def mock_api(method, url, auth, data=None):
if "/labels" in url:
return [{"name": "status:pr-open"}]
return {"number": 521, "html_url": "https://example/pr/521"}
mock_api_request.side_effect = mock_api
lock_res = gitea_lock_issue(
issue_number=521,
branch_name="feat/issue-521-lock-issue-registration",
remote="prgs",
worktree_path=worktree,
)
self.assertTrue(lock_res["success"])
lock_path = lock_res["lock_file_path"]
self.assertTrue(os.path.exists(lock_path))
lock = issue_lock_store.read_lock_file(lock_path)
self.assertEqual(lock["issue_number"], 521)
res = gitea_create_pr(
title="fix: restore lock tool registration Closes #521",
head="feat/issue-521-lock-issue-registration",
remote="prgs",
worktree_path=worktree,
)
self.assertEqual(res["number"], 521)