|
8 | 8 | from pydantic import AnyHttpUrl
|
9 | 9 | from starlette.applications import Starlette
|
10 | 10 |
|
11 |
| -from mcp.server.auth.routes import create_protected_resource_routes |
| 11 | +from mcp.server.auth.routes import build_resource_metadata_url, create_protected_resource_routes |
12 | 12 |
|
13 | 13 |
|
14 | 14 | @pytest.fixture
|
@@ -103,3 +103,96 @@ async def test_metadata_endpoint_without_path(root_resource_client: httpx.AsyncC
|
103 | 103 | "bearer_methods_supported": ["header"],
|
104 | 104 | }
|
105 | 105 | )
|
| 106 | + |
| 107 | + |
| 108 | +class TestMetadataUrlConstruction: |
| 109 | + """Test URL construction utility function.""" |
| 110 | + |
| 111 | + def test_url_without_path(self): |
| 112 | + """Test URL construction for resource without path component.""" |
| 113 | + resource_url = AnyHttpUrl("https://example.com") |
| 114 | + result = build_resource_metadata_url(resource_url) |
| 115 | + assert str(result) == "https://example.com/.well-known/oauth-protected-resource" |
| 116 | + |
| 117 | + def test_url_with_path_component(self): |
| 118 | + """Test URL construction for resource with path component.""" |
| 119 | + resource_url = AnyHttpUrl("https://example.com/mcp") |
| 120 | + result = build_resource_metadata_url(resource_url) |
| 121 | + assert str(result) == "https://example.com/.well-known/oauth-protected-resource/mcp" |
| 122 | + |
| 123 | + def test_url_with_trailing_slash_only(self): |
| 124 | + """Test URL construction for resource with trailing slash only.""" |
| 125 | + resource_url = AnyHttpUrl("https://example.com/") |
| 126 | + result = build_resource_metadata_url(resource_url) |
| 127 | + # Trailing slash should be treated as empty path |
| 128 | + assert str(result) == "https://example.com/.well-known/oauth-protected-resource" |
| 129 | + |
| 130 | + @pytest.mark.parametrize( |
| 131 | + "resource_url,expected_url", |
| 132 | + [ |
| 133 | + ("https://example.com", "https://example.com/.well-known/oauth-protected-resource"), |
| 134 | + ("https://example.com/", "https://example.com/.well-known/oauth-protected-resource"), |
| 135 | + ("https://example.com/mcp", "https://example.com/.well-known/oauth-protected-resource/mcp"), |
| 136 | + ("http://localhost:8001/mcp", "http://localhost:8001/.well-known/oauth-protected-resource/mcp"), |
| 137 | + ], |
| 138 | + ) |
| 139 | + def test_various_resource_configurations(self, resource_url: str, expected_url: str): |
| 140 | + """Test URL construction with various resource configurations.""" |
| 141 | + result = build_resource_metadata_url(AnyHttpUrl(resource_url)) |
| 142 | + assert str(result) == expected_url |
| 143 | + |
| 144 | + |
| 145 | +class TestRouteConsistency: |
| 146 | + """Test consistency between URL generation and route registration.""" |
| 147 | + |
| 148 | + def test_route_path_matches_metadata_url(self): |
| 149 | + """Test that route path matches the generated metadata URL.""" |
| 150 | + resource_url = AnyHttpUrl("https://example.com/mcp") |
| 151 | + |
| 152 | + # Generate metadata URL |
| 153 | + metadata_url = build_resource_metadata_url(resource_url) |
| 154 | + |
| 155 | + # Create routes |
| 156 | + routes = create_protected_resource_routes( |
| 157 | + resource_url=resource_url, |
| 158 | + authorization_servers=[AnyHttpUrl("https://auth.example.com")], |
| 159 | + ) |
| 160 | + |
| 161 | + # Extract path from metadata URL |
| 162 | + from urllib.parse import urlparse |
| 163 | + |
| 164 | + metadata_path = urlparse(str(metadata_url)).path |
| 165 | + |
| 166 | + # Verify consistency |
| 167 | + assert len(routes) == 1 |
| 168 | + assert routes[0].path == metadata_path |
| 169 | + |
| 170 | + @pytest.mark.parametrize( |
| 171 | + "resource_url,expected_path", |
| 172 | + [ |
| 173 | + ("https://example.com", "/.well-known/oauth-protected-resource"), |
| 174 | + ("https://example.com/", "/.well-known/oauth-protected-resource"), |
| 175 | + ("https://example.com/mcp", "/.well-known/oauth-protected-resource/mcp"), |
| 176 | + ], |
| 177 | + ) |
| 178 | + def test_consistent_paths_for_various_resources(self, resource_url: str, expected_path: str): |
| 179 | + """Test that URL generation and route creation are consistent.""" |
| 180 | + resource_url_obj = AnyHttpUrl(resource_url) |
| 181 | + |
| 182 | + # Test URL generation |
| 183 | + metadata_url = build_resource_metadata_url(resource_url_obj) |
| 184 | + from urllib.parse import urlparse |
| 185 | + |
| 186 | + url_path = urlparse(str(metadata_url)).path |
| 187 | + |
| 188 | + # Test route creation |
| 189 | + routes = create_protected_resource_routes( |
| 190 | + resource_url=resource_url_obj, |
| 191 | + authorization_servers=[AnyHttpUrl("https://auth.example.com")], |
| 192 | + ) |
| 193 | + route_path = routes[0].path |
| 194 | + |
| 195 | + # Both should match expected path |
| 196 | + assert url_path == expected_path |
| 197 | + assert route_path == expected_path |
| 198 | + assert url_path == route_path |
0 commit comments