Files
AegisGitea-MCP/tests/test_integration.py
latte 59e1ea53a8
Some checks failed
docker / lint (push) Has been cancelled
docker / test (push) Has been cancelled
docker / docker-build (push) Has been cancelled
lint / lint (push) Has been cancelled
test / test (push) Has been cancelled
Add OAuth2/OIDC per-user Gitea authentication
Introduce a GiteaOAuthValidator for JWT and userinfo validation and
fallbacks, add /oauth/token proxy, and thread per-user tokens through
the
request context and automation paths. Update config and .env.example for
OAuth-first mode, add OpenAPI, extensive unit/integration tests,
GitHub/Gitea CI workflows, docs, and lint/test enforcement (>=80% cov).
2026-02-25 16:54:01 +01:00

121 lines
4.0 KiB
Python

"""Integration tests for end-to-end MCP authentication behavior."""
from __future__ import annotations
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
from aegis_gitea_mcp.config import reset_settings
from aegis_gitea_mcp.oauth import reset_oauth_validator
@pytest.fixture(autouse=True)
def reset_state() -> None:
"""Reset global state between tests."""
reset_settings()
reset_oauth_validator()
yield
reset_settings()
reset_oauth_validator()
@pytest.fixture
def full_env(monkeypatch: pytest.MonkeyPatch) -> None:
"""Set OAuth-enabled environment for integration tests."""
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
monkeypatch.setenv("OAUTH_MODE", "true")
monkeypatch.setenv("GITEA_OAUTH_CLIENT_ID", "test-client-id")
monkeypatch.setenv("GITEA_OAUTH_CLIENT_SECRET", "test-client-secret")
monkeypatch.setenv("ENVIRONMENT", "test")
monkeypatch.setenv("MCP_HOST", "127.0.0.1")
monkeypatch.setenv("MCP_PORT", "8080")
monkeypatch.setenv("LOG_LEVEL", "INFO")
monkeypatch.setenv("STARTUP_VALIDATE_GITEA", "false")
@pytest.fixture
def client(full_env: None, monkeypatch: pytest.MonkeyPatch) -> TestClient:
"""Create test client with deterministic OAuth behavior."""
async def _validate(_self, token: str | None, _ip: str, _ua: str):
if token == "valid-read-token":
return True, None, {"login": "alice", "scopes": ["read:repository"]}
return False, "Invalid or expired OAuth token.", None
monkeypatch.setattr(
"aegis_gitea_mcp.oauth.GiteaOAuthValidator.validate_oauth_token",
_validate,
)
from aegis_gitea_mcp.server import app
return TestClient(app)
def test_no_token_returns_401_with_www_authenticate(client: TestClient) -> None:
"""Missing bearer token is rejected with OAuth challenge metadata."""
response = client.post(
"/mcp/tool/call",
json={"tool": "list_repositories", "arguments": {}},
)
assert response.status_code == 401
assert "WWW-Authenticate" in response.headers
assert "resource_metadata=" in response.headers["WWW-Authenticate"]
def test_invalid_token_returns_401(client: TestClient) -> None:
"""Invalid OAuth token is rejected."""
response = client.post(
"/mcp/tool/call",
headers={"Authorization": "Bearer invalid-token"},
json={"tool": "list_repositories", "arguments": {}},
)
assert response.status_code == 401
def test_valid_token_executes_tool(client: TestClient) -> None:
"""Valid OAuth token allows tool execution."""
with patch("aegis_gitea_mcp.gitea_client.GiteaClient.list_repositories") as mock_list_repos:
mock_list_repos.return_value = [{"id": 1, "name": "repo-one", "owner": {"login": "alice"}}]
response = client.post(
"/mcp/tool/call",
headers={"Authorization": "Bearer valid-read-token"},
json={"tool": "list_repositories", "arguments": {}},
)
assert response.status_code == 200
payload = response.json()
assert payload["success"] is True
assert "result" in payload
def test_write_scope_enforcement_returns_403(client: TestClient) -> None:
"""Write tool calls are denied when token lacks write scope."""
response = client.post(
"/mcp/tool/call",
headers={"Authorization": "Bearer valid-read-token"},
json={
"tool": "create_issue",
"arguments": {"owner": "acme", "repo": "demo", "title": "Needs write scope"},
},
)
assert response.status_code == 403
assert "required scope: write:repository" in response.json()["detail"].lower()
def test_error_responses_include_helpful_messages(client: TestClient) -> None:
"""Auth failures include actionable guidance."""
response = client.post(
"/mcp/tool/call",
json={"tool": "list_repositories", "arguments": {}},
)
assert response.status_code == 401
data = response.json()
assert "Provide Authorization" in data["message"]