Skip to content

Commit aabed62

Browse files
test: move OAuthClientMetadata empty-URL tests to tests/shared, drop Test class
1 parent 658c5b9 commit aabed62

File tree

2 files changed

+82
-75
lines changed

2 files changed

+82
-75
lines changed

tests/client/test_auth.py

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import httpx
1111
import pytest
1212
from inline_snapshot import Is, snapshot
13-
from pydantic import AnyHttpUrl, AnyUrl, ValidationError
13+
from pydantic import AnyHttpUrl, AnyUrl
1414

1515
from mcp.client.auth import OAuthClientProvider, PKCEParameters
1616
from mcp.client.auth.exceptions import OAuthFlowError
@@ -857,79 +857,6 @@ def text(self):
857857
assert "Registration failed: 400" in str(exc_info.value)
858858

859859

860-
class TestOAuthClientMetadataEmptyUrlCoercion:
861-
"""RFC 7591 §2 marks client_uri/logo_uri/tos_uri/policy_uri/jwks_uri as OPTIONAL.
862-
Some authorization servers echo the client's omitted metadata back as ""
863-
instead of dropping the keys; without coercion, AnyHttpUrl rejects "" and
864-
the whole registration response is thrown away even though the server
865-
returned a valid client_id."""
866-
867-
@pytest.mark.parametrize(
868-
"empty_field",
869-
["client_uri", "logo_uri", "tos_uri", "policy_uri", "jwks_uri"],
870-
)
871-
def test_optional_url_empty_string_coerced_to_none(self, empty_field: str):
872-
data = {
873-
"redirect_uris": ["https://example.com/callback"],
874-
empty_field: "",
875-
}
876-
metadata = OAuthClientMetadata.model_validate(data)
877-
assert getattr(metadata, empty_field) is None
878-
879-
def test_all_optional_urls_empty_together(self):
880-
data = {
881-
"redirect_uris": ["https://example.com/callback"],
882-
"client_uri": "",
883-
"logo_uri": "",
884-
"tos_uri": "",
885-
"policy_uri": "",
886-
"jwks_uri": "",
887-
}
888-
metadata = OAuthClientMetadata.model_validate(data)
889-
assert metadata.client_uri is None
890-
assert metadata.logo_uri is None
891-
assert metadata.tos_uri is None
892-
assert metadata.policy_uri is None
893-
assert metadata.jwks_uri is None
894-
895-
def test_valid_url_passes_through_unchanged(self):
896-
data = {
897-
"redirect_uris": ["https://example.com/callback"],
898-
"client_uri": "https://udemy.com/",
899-
}
900-
metadata = OAuthClientMetadata.model_validate(data)
901-
assert str(metadata.client_uri) == "https://udemy.com/"
902-
903-
def test_information_full_inherits_coercion(self):
904-
"""OAuthClientInformationFull subclasses OAuthClientMetadata, so the
905-
same coercion applies to DCR responses parsed via the full model."""
906-
data = {
907-
"client_id": "abc123",
908-
"redirect_uris": ["https://example.com/callback"],
909-
"client_uri": "",
910-
"logo_uri": "",
911-
"tos_uri": "",
912-
"policy_uri": "",
913-
"jwks_uri": "",
914-
}
915-
info = OAuthClientInformationFull.model_validate(data)
916-
assert info.client_id == "abc123"
917-
assert info.client_uri is None
918-
assert info.logo_uri is None
919-
assert info.tos_uri is None
920-
assert info.policy_uri is None
921-
assert info.jwks_uri is None
922-
923-
def test_invalid_non_empty_url_still_rejected(self):
924-
"""Coercion must only touch empty strings — garbage URLs still raise."""
925-
data = {
926-
"redirect_uris": ["https://example.com/callback"],
927-
"client_uri": "not a url",
928-
}
929-
with pytest.raises(ValidationError):
930-
OAuthClientMetadata.model_validate(data)
931-
932-
933860
class TestCreateClientRegistrationRequest:
934861
"""Test client registration request creation."""
935862

tests/shared/test_auth.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""Tests for OAuth 2.0 shared code."""
22

3-
from mcp.shared.auth import OAuthMetadata
3+
import pytest
4+
from pydantic import ValidationError
5+
6+
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata
47

58

69
class TestOAuthMetadata:
@@ -59,3 +62,80 @@ def test_oauth_with_jarm(self):
5962
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
6063
}
6164
)
65+
66+
67+
# RFC 7591 §2 marks client_uri/logo_uri/tos_uri/policy_uri/jwks_uri as OPTIONAL.
68+
# Some authorization servers echo the client's omitted metadata back as ""
69+
# instead of dropping the keys; without coercion, AnyHttpUrl rejects "" and
70+
# the whole registration response is thrown away even though the server
71+
# returned a valid client_id.
72+
73+
74+
@pytest.mark.parametrize(
75+
"empty_field",
76+
["client_uri", "logo_uri", "tos_uri", "policy_uri", "jwks_uri"],
77+
)
78+
def test_optional_url_empty_string_coerced_to_none(empty_field: str):
79+
data = {
80+
"redirect_uris": ["https://example.com/callback"],
81+
empty_field: "",
82+
}
83+
metadata = OAuthClientMetadata.model_validate(data)
84+
assert getattr(metadata, empty_field) is None
85+
86+
87+
def test_all_optional_urls_empty_together():
88+
data = {
89+
"redirect_uris": ["https://example.com/callback"],
90+
"client_uri": "",
91+
"logo_uri": "",
92+
"tos_uri": "",
93+
"policy_uri": "",
94+
"jwks_uri": "",
95+
}
96+
metadata = OAuthClientMetadata.model_validate(data)
97+
assert metadata.client_uri is None
98+
assert metadata.logo_uri is None
99+
assert metadata.tos_uri is None
100+
assert metadata.policy_uri is None
101+
assert metadata.jwks_uri is None
102+
103+
104+
def test_valid_url_passes_through_unchanged():
105+
data = {
106+
"redirect_uris": ["https://example.com/callback"],
107+
"client_uri": "https://udemy.com/",
108+
}
109+
metadata = OAuthClientMetadata.model_validate(data)
110+
assert str(metadata.client_uri) == "https://udemy.com/"
111+
112+
113+
def test_information_full_inherits_coercion():
114+
"""OAuthClientInformationFull subclasses OAuthClientMetadata, so the
115+
same coercion applies to DCR responses parsed via the full model."""
116+
data = {
117+
"client_id": "abc123",
118+
"redirect_uris": ["https://example.com/callback"],
119+
"client_uri": "",
120+
"logo_uri": "",
121+
"tos_uri": "",
122+
"policy_uri": "",
123+
"jwks_uri": "",
124+
}
125+
info = OAuthClientInformationFull.model_validate(data)
126+
assert info.client_id == "abc123"
127+
assert info.client_uri is None
128+
assert info.logo_uri is None
129+
assert info.tos_uri is None
130+
assert info.policy_uri is None
131+
assert info.jwks_uri is None
132+
133+
134+
def test_invalid_non_empty_url_still_rejected():
135+
"""Coercion must only touch empty strings — garbage URLs still raise."""
136+
data = {
137+
"redirect_uris": ["https://example.com/callback"],
138+
"client_uri": "not a url",
139+
}
140+
with pytest.raises(ValidationError):
141+
OAuthClientMetadata.model_validate(data)

0 commit comments

Comments
 (0)