"""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 from unittest.mock import patch 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 def test_401_includes_www_authenticate_header(): """RFC 7235 requires WWW-Authenticate on 401 so clients can tell why they failed.""" 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 assert response.headers["WWW-Authenticate"] == "Bearer" def test_non_utf8_authorization_header_is_rejected_not_crashed(): """A non-UTF-8 header byte must yield a clean 401, not an unhandled 500. ASGI header values are latin-1 bytes, so any remote client could otherwise trigger a UnicodeDecodeError inside the middleware at will. """ with patch("app.mcp.server.settings") as fake_settings: fake_settings.auth_enabled = True client = TestClient(_dummy_app(), raise_server_exceptions=False) # Bypass the http client's own header encoding by writing raw bytes. response = client.get("/ping", headers={"Authorization": b"Bearer \xff\xfe"}) assert response.status_code == 401