""" OAuth discovery metadata tests (RFC 9728 / RFC 8414 / RFC 7591). MCP clients that get a 401 from /mcp perform OAuth discovery. These endpoints are unauthenticated and served straight from main.app. """ import httpx import pytest import main from config import get_settings @pytest.fixture async def client(monkeypatch): # These endpoints derive their URLs from PUBLIC_BASE_URL when it is set, # falling back to the request's Host header. Pin it empty so the assertions # below exercise the header path and can't be overridden by a developer's # real .env. monkeypatch.setattr(get_settings(), "public_base_url", "") transport = httpx.ASGITransport(app=main.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as c: yield c class TestProtectedResourceMetadata: async def test_resource_advertises_mcp_path(self, client): resp = await client.get("/.well-known/oauth-protected-resource") assert resp.status_code == 200 body = resp.json() # mcp-remote verifies this matches the URL it connected to. assert body["resource"] == "http://test/mcp" assert body["authorization_servers"] == ["http://test"] async def test_mcp_suffixed_variant(self, client): resp = await client.get("/.well-known/oauth-protected-resource/mcp") assert resp.status_code == 200 assert resp.json()["resource"] == "http://test/mcp" class TestAuthorizationServerMetadata: async def test_advertises_casdoor_when_enabled(self, monkeypatch, client): monkeypatch.setattr(get_settings().casdoor, "enabled", True) monkeypatch.setattr(get_settings().casdoor, "endpoint", "https://id.example.test") resp = await client.get("/.well-known/oauth-authorization-server") assert resp.status_code == 200 body = resp.json() assert body["issuer"] == "https://id.example.test" assert body["jwks_uri"] == "https://id.example.test/.well-known/jwks" assert body["registration_endpoint"] == "http://test/register" async def test_dev_mode_advertises_local(self, monkeypatch, client): monkeypatch.setattr(get_settings().casdoor, "enabled", False) resp = await client.get("/.well-known/oauth-authorization-server") assert resp.status_code == 200 body = resp.json() assert body["issuer"] == "http://test" assert body["authorization_endpoint"] == "http://test/auth/login" class TestDynamicRegistration: async def test_registers_client(self, client): resp = await client.post( "/register", json={"redirect_uris": ["http://localhost/cb"], "client_name": "test"}, ) assert resp.status_code == 201 body = resp.json() assert "client_id" in body assert body["redirect_uris"] == ["http://localhost/cb"] async def test_rejects_missing_redirect_uris(self, client): resp = await client.post("/register", json={"client_name": "test"}) assert resp.status_code == 400 assert resp.json()["error"] == "invalid_redirect_uri" async def test_rejects_non_json(self, client): resp = await client.post( "/register", content=b"not json", headers={"content-type": "application/json"} ) assert resp.status_code == 400