"""Unit tests for MCPAuthMiddleware. Wraps a minimal dummy ASGI app (not the real MCP app) so these tests exercise only the auth gate, not the MCP protocol itself — keeps the test fast and independent of FastMCP internals. """ from __future__ import annotations import sys from unittest.mock import MagicMock, patch # app.mcp.server imports app.shared.bootstrap at module scope (needed for # get_agent_conversation_service/get_jwt_handler), which in turn eagerly # imports several Postgres store modules that do `import psycopg2` at their # own module scope. Since this is the only test file in backend/tests/mcp/ # that imports app.mcp.server at module scope (not inside a test function), # it is the first thing to trigger that chain during pytest collection — # guard psycopg2 here the same way backend/tests/observability/ # test_model_usage_bootstrap.py and test_model_usage_persistence.py already # do, so a real (network-connecting) psycopg2 never gets bound first. mock_psycopg2 = MagicMock() mock_psycopg2.extras = MagicMock() sys.modules.setdefault("psycopg2", mock_psycopg2) sys.modules.setdefault("psycopg2.extras", mock_psycopg2.extras) sys.modules.setdefault("psycopg2.pool", MagicMock()) from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.routing import Route from starlette.testclient import TestClient from app.mcp.server import MCPAuthMiddleware def _dummy_app() -> Starlette: """Build a minimal Starlette app that MCPAuthMiddleware can wrap.""" async def _ok(request): """Return a fixed 200 response so tests can assert pass-through.""" return PlainTextResponse("ok") app = Starlette(routes=[Route("/ping", _ok)]) app.add_middleware(MCPAuthMiddleware) return app def test_missing_token_rejected_when_auth_enabled(): """No Authorization header + auth_enabled=True -> 401.""" with patch("app.mcp.server.settings") as fake_settings: fake_settings.auth_enabled = True client = TestClient(_dummy_app()) response = client.get("/ping") assert response.status_code == 401 def test_invalid_token_rejected_when_auth_enabled(): """A token that fails decode_token() -> 401, request never reaches the app.""" fake_handler = type("H", (), {"decode_token": lambda self, t: (_ for _ in ()).throw(ValueError("bad token"))})() with patch("app.mcp.server.settings") as fake_settings, \ patch("app.mcp.server.get_jwt_handler", return_value=fake_handler): fake_settings.auth_enabled = True client = TestClient(_dummy_app()) response = client.get("/ping", headers={"Authorization": "Bearer garbage"}) assert response.status_code == 401 def test_valid_token_passes_through_when_auth_enabled(): """A token that decodes successfully -> request reaches the wrapped app.""" fake_handler = type("H", (), {"decode_token": lambda self, t: object()})() with patch("app.mcp.server.settings") as fake_settings, \ patch("app.mcp.server.get_jwt_handler", return_value=fake_handler): fake_settings.auth_enabled = True client = TestClient(_dummy_app()) response = client.get("/ping", headers={"Authorization": "Bearer good"}) assert response.status_code == 200 assert response.text == "ok" def test_auth_disabled_always_passes_through(): """auth_enabled=False (dev mode) -> no token needed, matches get_current_user's dev bypass.""" with patch("app.mcp.server.settings") as fake_settings: fake_settings.auth_enabled = False client = TestClient(_dummy_app()) response = client.get("/ping") assert response.status_code == 200