refactor: extract transport-agnostic core and shared tool registry

Introduce aegis_gitea_mcp.registry as the single name->handler source of
truth consumed by every transport adapter, moving TOOL_HANDLERS out of the
FastAPI server module. Add aegis_gitea_mcp.errors.ToolError so core handlers
no longer import fastapi.HTTPException; raw_tools now raises ToolError and the
HTTP adapter maps it back to HTTPException, preserving status codes and audit
behavior. Add a subprocess boundary test asserting the core imports without
pulling in fastapi/uvicorn/starlette.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 10:49:46 +02:00
parent dd253f87e5
commit 7da0c46de8
6 changed files with 262 additions and 122 deletions
+6 -6
View File
@@ -6,10 +6,10 @@ from pathlib import Path
from typing import Any
import pytest
from fastapi import HTTPException
from pydantic import ValidationError
from aegis_gitea_mcp.config import reset_settings
from aegis_gitea_mcp.errors import ToolError
from aegis_gitea_mcp.tools.arguments import (
extract_repository,
extract_target_path,
@@ -83,7 +83,7 @@ async def test_lowercase_method_is_normalized(raw_env: None) -> None:
async def test_delete_denied_when_write_mode_off(raw_env: None) -> None:
"""A write method is denied (no network call) while write-mode is disabled."""
stub = StubRawGitea()
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(ToolError) as exc_info:
await raw_api_request_tool(stub, {"method": "DELETE", "path": "/repos/acme/app/issues/1"})
assert exc_info.value.status_code == 403
@@ -135,7 +135,7 @@ async def test_write_denied_for_repo_outside_whitelist(
monkeypatch.setenv("WRITE_REPOSITORY_WHITELIST", "acme/other")
stub = StubRawGitea()
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(ToolError) as exc_info:
await raw_api_request_tool(stub, {"method": "POST", "path": "/repos/acme/app/issues"})
assert exc_info.value.status_code == 403
@@ -158,7 +158,7 @@ async def test_non_repository_write_denied(monkeypatch: pytest.MonkeyPatch, tmp_
monkeypatch.setenv("WRITE_REPOSITORY_WHITELIST", "acme/app")
stub = StubRawGitea()
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(ToolError) as exc_info:
await raw_api_request_tool(stub, {"method": "POST", "path": "/user/repos"})
assert exc_info.value.status_code == 403
@@ -173,7 +173,7 @@ async def test_non_repository_write_denied(monkeypatch: pytest.MonkeyPatch, tmp_
async def test_sensitive_paths_denied_on_get(raw_env: None, path: str) -> None:
"""Admin/credential surfaces are denied for every method, including GET."""
stub = StubRawGitea()
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(ToolError) as exc_info:
await raw_api_request_tool(stub, {"method": "GET", "path": path})
assert exc_info.value.status_code == 403
@@ -253,7 +253,7 @@ async def test_raw_api_disabled(monkeypatch: pytest.MonkeyPatch, tmp_path: Path)
monkeypatch.setenv("RAW_API_ENABLED", "false")
stub = StubRawGitea()
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(ToolError) as exc_info:
await raw_api_request_tool(stub, {"method": "GET", "path": "/repos/acme/app"})
assert exc_info.value.status_code == 403
assert "disabled" in str(exc_info.value.detail)