Skip to content

Commit 7552e9d

Browse files
committed
test: add tests
1 parent 5d70ff3 commit 7552e9d

15 files changed

Lines changed: 279 additions & 20 deletions

mcpauth/auth/authorization_server_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ def get_token_verifier(self, resource: str) -> TokenVerifier:
9090
This is a dummy implementation that ignores the resource, as there is only
9191
one `TokenVerifier` in the authorization server mode.
9292
"""
93-
return self.token_verifier
93+
return self.token_verifier

mcpauth/auth/mcp_auth_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ def create_metadata_route(self) -> Router:
1717
Returns a router for serving either the legacy OAuth 2.0 Authorization Server Metadata or
1818
the OAuth 2.0 Protected Resource Metadata, depending on the configuration.
1919
"""
20-
...
20+
... # pragma: no cover
2121

2222
@abstractmethod
2323
def get_token_verifier(self, resource: str) -> TokenVerifier:
2424
"""
2525
Resolves the appropriate TokenVerifier based on the provided resource.
2626
:param resource: The resource identifier for verifier lookup.
2727
"""
28-
...
28+
... # pragma: no cover

mcpauth/auth/resource_server_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,4 @@ def _validate_config(self, resource_configs: List[ResourceServerConfig]):
116116
cause={"error_description": f"The authorization server ('{issuer}') for resource '{resource}' is duplicated."},
117117
)
118118
unique_auth_servers.add(issuer)
119-
validate_server_config(auth_server)
119+
validate_server_config(auth_server)

mcpauth/auth/token_verifier.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,4 @@ def verify_jwt(token: str) -> AuthInfo:
8585
verify_function = create_verify_jwt(jwks_uri, leeway=leeway)
8686
return verify_function(token)
8787

88-
return verify_jwt
88+
return verify_jwt

mcpauth/middleware/create_bearer_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def _handle_error(
106106
www_authenticate_header = BearerWWWAuthenticateHeader()
107107

108108
if isinstance(error, (MCPAuthTokenVerificationException, MCPAuthBearerAuthException)):
109-
www_authenticate_header.set_parameter_if_value_exists("error", error.code)
109+
www_authenticate_header.set_parameter_if_value_exists("error", error.code.value)
110110
if error.message:
111111
www_authenticate_header.set_parameter_if_value_exists("error_description", error.message)
112112

mcpauth/utils/_transpile_resource_metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ def transpile_resource_metadata(
3131
if auth_servers:
3232
model_data["authorization_servers"] = auth_servers
3333

34-
return ProtectedResourceMetadata(**model_data)
34+
return ProtectedResourceMetadata(**model_data)

samples/server/todo-manager/server.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
MCPAuthBearerAuthException,
2525
BearerAuthExceptionCode,
2626
)
27-
from mcpauth.types import AuthInfo
27+
from mcpauth.types import AuthInfo, ResourceServerConfig, ResourceServerMetadata
2828
from mcpauth.utils import fetch_server_config
2929
from .service import TodoService
3030

@@ -44,7 +44,22 @@
4444
)
4545

4646
auth_server_config = fetch_server_config(auth_issuer, AuthServerType.OIDC)
47-
mcp_auth = MCPAuth(server=auth_server_config)
47+
resource_id = "https://todo-manager.mcp-auth.com/resource1"
48+
mcp_auth = MCPAuth(
49+
protected_resources=[
50+
ResourceServerConfig(
51+
metadata=ResourceServerMetadata(
52+
resource=resource_id,
53+
authorization_servers=[auth_server_config],
54+
scopes_supported=[
55+
"create:todos",
56+
"read:todos",
57+
"delete:todos",
58+
],
59+
)
60+
)
61+
]
62+
)
4863

4964
def assert_user_id(auth_info: Optional[AuthInfo]) -> str:
5065
"""Assert that auth_info contains a valid user ID and return it."""
@@ -122,12 +137,11 @@ def delete_todo(id: str) -> dict[str, Any]:
122137
return {"error": "Failed to delete todo"}
123138

124139
# Create the middleware and app
125-
bearer_auth = Middleware(mcp_auth.bearer_auth_middleware('jwt'))
140+
bearer_auth = Middleware(mcp_auth.bearer_auth_middleware('jwt', resource=resource_id))
126141
app = Starlette(
127142
routes=[
128-
# Add the metadata route (`/.well-known/oauth-authorization-server`)
129-
mcp_auth.metadata_route(), # pyright: ignore[reportDeprecated]
130143
# Protect the MCP server with the Bearer auth middleware
144+
*mcp_auth.resource_metadata_router().routes,
131145
Mount("/", app=mcp.sse_app(), middleware=[bearer_auth]),
132146
],
133147
)

tests/__init__test.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ def test_bearer_auth_middleware_calls_get_token_verifier_in_resource_mode(
124124
mock_get_verifier.assert_called_once_with(resource="https://api.example.com")
125125

126126

127+
def test_bearer_auth_middleware_throws_for_invalid_mode(
128+
valid_server_config: AuthServerConfig,
129+
):
130+
"""Test that bearer_auth_middleware throws a ValueError for an invalid mode."""
131+
auth = MCPAuth(server=valid_server_config)
132+
with pytest.raises(
133+
ValueError,
134+
match="mode_or_verify must be 'jwt' or a callable function that verifies tokens.",
135+
):
136+
auth.bearer_auth_middleware(mode_or_verify="invalid_mode") # type: ignore
137+
138+
127139
@patch("mcpauth.auth.resource_server_handler.validate_server_config")
128140
def test_metadata_route_throws_in_resource_mode(
129141
mock_validate: MagicMock, valid_resource_config: ResourceServerConfig
@@ -164,6 +176,26 @@ def test_metadata_route_calls_handler_method(
164176
mock_create_route.assert_called_once()
165177

166178

179+
@patch(
180+
"mcpauth.auth.authorization_server_handler.AuthorizationServerHandler.create_metadata_route"
181+
)
182+
@patch("mcpauth.auth.authorization_server_handler.validate_server_config")
183+
def test_metadata_route_throws_if_route_is_not_route_instance(
184+
mock_validate: MagicMock,
185+
mock_create_route: MagicMock,
186+
valid_server_config: AuthServerConfig,
187+
):
188+
"""Test that metadata_route throws an error if the created route is not a Route instance."""
189+
# Ensure the mock returns a router-like object with a routes attribute
190+
# containing something that is not a Route instance
191+
mock_create_route.return_value = MagicMock(routes=[MagicMock()])
192+
auth = MCPAuth(server=valid_server_config)
193+
with pytest.warns(DeprecationWarning):
194+
with pytest.raises(IndexError, match="No metadata endpoint route was created"):
195+
auth.metadata_route() # pyright: ignore[reportDeprecated]
196+
mock_create_route.assert_called_once()
197+
198+
167199
@patch(
168200
"mcpauth.auth.resource_server_handler.ResourceServerHandler.create_metadata_route"
169201
)
@@ -176,4 +208,20 @@ def test_resource_metadata_router_calls_handler_method(
176208
"""Test that resource_metadata_router calls the handler's create_metadata_route method."""
177209
auth = MCPAuth(protected_resources=valid_resource_config)
178210
auth.resource_metadata_router()
179-
mock_create_route.assert_called_once()
211+
mock_create_route.assert_called_once()
212+
213+
214+
@patch("mcpauth.middleware.create_bearer_auth.create_bearer_auth")
215+
def test_bearer_auth_middleware_with_callable_verifier(
216+
mock_create_bearer_auth: MagicMock, valid_server_config: AuthServerConfig
217+
):
218+
"""Test that bearer_auth_middleware works with a callable verifier."""
219+
auth = MCPAuth(server=valid_server_config)
220+
verifier = MagicMock()
221+
with patch("mcpauth.MCPAuthHandler.get_token_verifier"):
222+
auth.bearer_auth_middleware(mode_or_verify=verifier)
223+
224+
mock_create_bearer_auth.assert_called_once()
225+
# Check that the verifier is passed to create_bearer_auth
226+
args, _ = mock_create_bearer_auth.call_args
227+
assert args[0] == verifier

tests/auth/authorization_server_handler_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,4 @@ def test_get_token_verifier(valid_auth_server_config: AuthServerConfig):
150150
AuthServerModeConfig(server=valid_auth_server_config)
151151
)
152152
verifier = handler.get_token_verifier("test-resource")
153-
assert verifier == handler.token_verifier
153+
assert verifier == handler.token_verifier

tests/auth/resource_server_handler_test.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,33 @@ def test_init_duplicate_resource_id(mock_validate: Mock, mock_resource_config: R
9191
assert excinfo.value.code == AuthServerExceptionCode.INVALID_SERVER_CONFIG
9292

9393

94+
@patch(
95+
"mcpauth.auth.resource_server_handler.validate_server_config",
96+
return_value=type("ValidationResult", (), {"is_valid": True}),
97+
)
98+
def test_init_duplicate_auth_server(
99+
mock_validate: Mock, mock_auth_server: AuthServerConfig
100+
):
101+
"""Test that ResourceServerHandler throws an error if an auth server is duplicated for a resource."""
102+
config_with_duplicate_auth_server = RSC(
103+
metadata=ResourceServerMetadata(
104+
resource="https://my-api.com",
105+
authorization_servers=[mock_auth_server, mock_auth_server],
106+
)
107+
)
108+
with pytest.raises(MCPAuthAuthServerException) as excinfo:
109+
ResourceServerHandler(
110+
ResourceServerModeConfig(
111+
protected_resources=[config_with_duplicate_auth_server]
112+
)
113+
)
114+
assert excinfo.value.code == AuthServerExceptionCode.INVALID_SERVER_CONFIG
115+
assert (
116+
excinfo.value.cause["error_description"] # type: ignore[reportGeneralTypeIssues]
117+
== "The authorization server ('https://auth.example.com') for resource 'https://my-api.com' is duplicated."
118+
)
119+
120+
94121
@patch(
95122
"mcpauth.auth.resource_server_handler.validate_server_config",
96123
return_value=type("ValidationResult", (), {"is_valid": True}),
@@ -153,4 +180,4 @@ def test_create_metadata_route(mock_validate: Mock, mock_resource_config: RSC):
153180
assert response.json()["authorization_servers"] == ["https://auth.example.com"]
154181

155182
response = client.options(endpoint_path)
156-
assert response.status_code == 204
183+
assert response.status_code == 204

0 commit comments

Comments
 (0)