Replace httpx and httpx-sse with httpx2 (#2972)
Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
This commit is contained in:
parent
1216c53693
commit
2713b53b12
1 changed files with 1029 additions and 948 deletions
|
|
@ -6,7 +6,7 @@ import time
|
|||
from unittest import mock
|
||||
from urllib.parse import parse_qs, quote, unquote, urlparse
|
||||
|
||||
import httpx
|
||||
import httpx2
|
||||
import pytest
|
||||
from inline_snapshot import Is, snapshot
|
||||
from pydantic import AnyHttpUrl, AnyUrl
|
||||
|
|
@ -111,7 +111,7 @@ def oauth_provider(client_metadata: OAuthClientMetadata, mock_storage: MockToken
|
|||
@pytest.fixture
|
||||
def prm_metadata_response():
|
||||
"""PRM metadata response with scopes."""
|
||||
return httpx.Response(
|
||||
return httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"resource": "https://api.example.com/v1/mcp", '
|
||||
|
|
@ -124,7 +124,7 @@ def prm_metadata_response():
|
|||
@pytest.fixture
|
||||
def prm_metadata_without_scopes_response():
|
||||
"""PRM metadata response without scopes."""
|
||||
return httpx.Response(
|
||||
return httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"resource": "https://api.example.com/v1/mcp", '
|
||||
|
|
@ -137,20 +137,20 @@ def prm_metadata_without_scopes_response():
|
|||
@pytest.fixture
|
||||
def init_response_with_www_auth_scope():
|
||||
"""Initial 401 response with WWW-Authenticate header containing scope."""
|
||||
return httpx.Response(
|
||||
return httpx2.Response(
|
||||
401,
|
||||
headers={"WWW-Authenticate": 'Bearer scope="special:scope from:www-authenticate"'},
|
||||
request=httpx.Request("GET", "https://api.example.com/test"),
|
||||
request=httpx2.Request("GET", "https://api.example.com/test"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def init_response_without_www_auth_scope():
|
||||
"""Initial 401 response without WWW-Authenticate scope."""
|
||||
return httpx.Response(
|
||||
return httpx2.Response(
|
||||
401,
|
||||
headers={},
|
||||
request=httpx.Request("GET", "https://api.example.com/test"),
|
||||
request=httpx2.Request("GET", "https://api.example.com/test"),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -290,8 +290,8 @@ class TestOAuthFlow:
|
|||
)
|
||||
|
||||
# Test without WWW-Authenticate (fallback)
|
||||
init_response = httpx.Response(
|
||||
status_code=401, headers={}, request=httpx.Request("GET", "https://request-api.example.com")
|
||||
init_response = httpx2.Response(
|
||||
status_code=401, headers={}, request=httpx2.Request("GET", "https://request-api.example.com")
|
||||
)
|
||||
|
||||
urls = build_protected_resource_metadata_discovery_urls(
|
||||
|
|
@ -408,7 +408,7 @@ class TestOAuthFallback:
|
|||
)
|
||||
|
||||
# Create a test request
|
||||
test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
|
||||
# Mock the auth flow
|
||||
auth_flow = oauth_provider.async_auth_flow(test_request)
|
||||
|
|
@ -418,7 +418,7 @@ class TestOAuthFallback:
|
|||
assert "Authorization" not in request.headers
|
||||
|
||||
# Send a 401 response to trigger the OAuth flow
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
401,
|
||||
headers={
|
||||
"WWW-Authenticate": 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"'
|
||||
|
|
@ -433,7 +433,7 @@ class TestOAuthFallback:
|
|||
|
||||
# Send a successful discovery response with minimal protected resource metadata
|
||||
# Note: auth server URL has a path (/v1/mcp), so only path-based URLs will be tried
|
||||
discovery_response = httpx.Response(
|
||||
discovery_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com/v1/mcp"]}',
|
||||
request=discovery_request,
|
||||
|
|
@ -448,7 +448,7 @@ class TestOAuthFallback:
|
|||
assert oauth_metadata_request_1.method == "GET"
|
||||
|
||||
# Send a 404 response
|
||||
oauth_metadata_response_1 = httpx.Response(
|
||||
oauth_metadata_response_1 = httpx2.Response(
|
||||
404,
|
||||
content=b"Not Found",
|
||||
request=oauth_metadata_request_1,
|
||||
|
|
@ -460,7 +460,7 @@ class TestOAuthFallback:
|
|||
assert oauth_metadata_request_2.method == "GET"
|
||||
|
||||
# Send a 400 response
|
||||
oauth_metadata_response_2 = httpx.Response(
|
||||
oauth_metadata_response_2 = httpx2.Response(
|
||||
400,
|
||||
content=b"Bad Request",
|
||||
request=oauth_metadata_request_2,
|
||||
|
|
@ -472,7 +472,7 @@ class TestOAuthFallback:
|
|||
assert oauth_metadata_request_3.method == "GET"
|
||||
|
||||
# Send a 500 response
|
||||
oauth_metadata_response_3 = httpx.Response(
|
||||
oauth_metadata_response_3 = httpx2.Response(
|
||||
500,
|
||||
content=b"Internal Server Error",
|
||||
request=oauth_metadata_request_3,
|
||||
|
|
@ -490,7 +490,7 @@ class TestOAuthFallback:
|
|||
assert token_request.method == "POST"
|
||||
|
||||
# Send a successful token response
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600, '
|
||||
|
|
@ -506,7 +506,7 @@ class TestOAuthFallback:
|
|||
assert str(final_request.url) == "https://api.example.com/v1/mcp"
|
||||
|
||||
# Send final success response to properly close the generator
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -521,7 +521,7 @@ class TestOAuthFallback:
|
|||
"authorization_endpoint": "https://auth.example.com/authorize",
|
||||
"token_endpoint": "https://auth.example.com/token"
|
||||
}"""
|
||||
response = httpx.Response(200, content=content)
|
||||
response = httpx2.Response(200, content=content)
|
||||
|
||||
# Should set metadata; the empty path is preserved (no trailing slash added)
|
||||
await oauth_provider._handle_oauth_metadata_response(response)
|
||||
|
|
@ -532,8 +532,8 @@ class TestOAuthFallback:
|
|||
async def test_prioritize_www_auth_scope_over_prm(
|
||||
self,
|
||||
oauth_provider: OAuthClientProvider,
|
||||
prm_metadata_response: httpx.Response,
|
||||
init_response_with_www_auth_scope: httpx.Response,
|
||||
prm_metadata_response: httpx2.Response,
|
||||
init_response_with_www_auth_scope: httpx2.Response,
|
||||
):
|
||||
"""Test that WWW-Authenticate scope is prioritized over PRM scopes."""
|
||||
# First, process PRM metadata to set protected_resource_metadata with scopes
|
||||
|
|
@ -552,8 +552,8 @@ class TestOAuthFallback:
|
|||
async def test_prioritize_prm_scopes_when_no_www_auth_scope(
|
||||
self,
|
||||
oauth_provider: OAuthClientProvider,
|
||||
prm_metadata_response: httpx.Response,
|
||||
init_response_without_www_auth_scope: httpx.Response,
|
||||
prm_metadata_response: httpx2.Response,
|
||||
init_response_without_www_auth_scope: httpx2.Response,
|
||||
):
|
||||
"""Test that PRM scopes are prioritized when WWW-Authenticate header has no scopes."""
|
||||
# Process the PRM metadata to set protected_resource_metadata with scopes
|
||||
|
|
@ -572,8 +572,8 @@ class TestOAuthFallback:
|
|||
async def test_omit_scope_when_no_prm_scopes_or_www_auth(
|
||||
self,
|
||||
oauth_provider: OAuthClientProvider,
|
||||
prm_metadata_without_scopes_response: httpx.Response,
|
||||
init_response_without_www_auth_scope: httpx.Response,
|
||||
prm_metadata_without_scopes_response: httpx2.Response,
|
||||
init_response_without_www_auth_scope: httpx2.Response,
|
||||
):
|
||||
"""Test that scope is omitted when PRM has no scopes and WWW-Authenticate doesn't specify scope."""
|
||||
# Process the PRM metadata without scopes
|
||||
|
|
@ -981,7 +981,7 @@ class TestRegistrationResponse:
|
|||
"""Test that response.aread() is called before accessing response.text."""
|
||||
|
||||
# Track if aread() was called
|
||||
class MockResponse(httpx.Response):
|
||||
class MockResponse(httpx2.Response):
|
||||
def __init__(self):
|
||||
self.status_code = 400
|
||||
self._aread_called = False
|
||||
|
|
@ -1068,7 +1068,7 @@ def test_registration_request_sends_application_type():
|
|||
|
||||
|
||||
class TestAuthFlow:
|
||||
"""Test the auth flow in httpx."""
|
||||
"""Test the auth flow in httpx2."""
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_auth_flow_with_valid_tokens(
|
||||
|
|
@ -1082,7 +1082,7 @@ class TestAuthFlow:
|
|||
oauth_provider._initialized = True
|
||||
|
||||
# Create a test request
|
||||
test_request = httpx.Request("GET", "https://api.example.com/test")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/test")
|
||||
|
||||
# Mock the auth flow
|
||||
auth_flow = oauth_provider.async_auth_flow(test_request)
|
||||
|
|
@ -1092,7 +1092,7 @@ class TestAuthFlow:
|
|||
assert request.headers["Authorization"] == "Bearer test_access_token"
|
||||
|
||||
# Send a successful response
|
||||
response = httpx.Response(200)
|
||||
response = httpx2.Response(200)
|
||||
try:
|
||||
await auth_flow.asend(response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -1107,7 +1107,7 @@ class TestAuthFlow:
|
|||
oauth_provider._initialized = True
|
||||
|
||||
# Create a test request
|
||||
test_request = httpx.Request("GET", "https://api.example.com/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/mcp")
|
||||
|
||||
# Mock the auth flow
|
||||
auth_flow = oauth_provider.async_auth_flow(test_request)
|
||||
|
|
@ -1117,7 +1117,7 @@ class TestAuthFlow:
|
|||
assert "Authorization" not in request.headers
|
||||
|
||||
# Send a 401 response to trigger the OAuth flow
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
401,
|
||||
headers={
|
||||
"WWW-Authenticate": 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"'
|
||||
|
|
@ -1131,7 +1131,7 @@ class TestAuthFlow:
|
|||
assert str(discovery_request.url) == "https://api.example.com/.well-known/oauth-protected-resource"
|
||||
|
||||
# Send a successful discovery response with minimal protected resource metadata
|
||||
discovery_response = httpx.Response(
|
||||
discovery_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com"]}',
|
||||
request=discovery_request,
|
||||
|
|
@ -1144,7 +1144,7 @@ class TestAuthFlow:
|
|||
assert "mcp-protocol-version" in oauth_metadata_request.headers
|
||||
|
||||
# Send a successful OAuth metadata response
|
||||
oauth_metadata_response = httpx.Response(
|
||||
oauth_metadata_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://auth.example.com", '
|
||||
|
|
@ -1161,7 +1161,7 @@ class TestAuthFlow:
|
|||
assert str(registration_request.url) == "https://auth.example.com/register"
|
||||
|
||||
# Send a successful registration response
|
||||
registration_response = httpx.Response(
|
||||
registration_response = httpx2.Response(
|
||||
201,
|
||||
content=b'{"client_id": "test_client_id", "client_secret": "test_client_secret", "redirect_uris": ["http://localhost:3030/callback"]}',
|
||||
request=registration_request,
|
||||
|
|
@ -1179,7 +1179,7 @@ class TestAuthFlow:
|
|||
assert "code=test_auth_code" in token_request.content.decode()
|
||||
|
||||
# Send a successful token response
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600, '
|
||||
|
|
@ -1195,7 +1195,7 @@ class TestAuthFlow:
|
|||
assert str(final_request.url) == "https://api.example.com/mcp"
|
||||
|
||||
# Send final success response to properly close the generator
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -1217,7 +1217,7 @@ class TestAuthFlow:
|
|||
oauth_provider.context.token_expiry_time = time.time() + 1800
|
||||
oauth_provider._initialized = True
|
||||
|
||||
test_request = httpx.Request("GET", "https://api.example.com/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/mcp")
|
||||
auth_flow = oauth_provider.async_auth_flow(test_request)
|
||||
|
||||
# Count how many times the request is yielded
|
||||
|
|
@ -1229,7 +1229,7 @@ class TestAuthFlow:
|
|||
assert request.headers["Authorization"] == "Bearer test_access_token"
|
||||
|
||||
# Send a successful 200 response
|
||||
response = httpx.Response(200, request=request)
|
||||
response = httpx2.Response(200, request=request)
|
||||
|
||||
# In the buggy version, this would yield the request AGAIN unconditionally
|
||||
# In the fixed version, this should end the generator
|
||||
|
|
@ -1260,7 +1260,7 @@ class TestAuthFlow:
|
|||
oauth_provider._initialized = True
|
||||
|
||||
# Create a test request
|
||||
test_request = httpx.Request("GET", "https://api.example.com/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/mcp")
|
||||
|
||||
# Mock the auth flow
|
||||
auth_flow = oauth_provider.async_auth_flow(test_request)
|
||||
|
|
@ -1270,7 +1270,7 @@ class TestAuthFlow:
|
|||
assert "Authorization" not in request.headers
|
||||
|
||||
# Send a 401 response to trigger the OAuth flow
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
401,
|
||||
headers={
|
||||
"WWW-Authenticate": 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"'
|
||||
|
|
@ -1284,7 +1284,7 @@ class TestAuthFlow:
|
|||
assert str(discovery_request.url) == "https://api.example.com/.well-known/oauth-protected-resource"
|
||||
|
||||
# Send a successful discovery response with minimal protected resource metadata
|
||||
discovery_response = httpx.Response(
|
||||
discovery_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com"]}',
|
||||
request=discovery_request,
|
||||
|
|
@ -1297,7 +1297,7 @@ class TestAuthFlow:
|
|||
assert "mcp-protocol-version" in oauth_metadata_request.headers
|
||||
|
||||
# Send a successful OAuth metadata response
|
||||
oauth_metadata_response = httpx.Response(
|
||||
oauth_metadata_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://auth.example.com", '
|
||||
|
|
@ -1314,7 +1314,7 @@ class TestAuthFlow:
|
|||
assert str(registration_request.url) == "https://auth.example.com/register"
|
||||
|
||||
# Send a successful registration response with 201 status
|
||||
registration_response = httpx.Response(
|
||||
registration_response = httpx2.Response(
|
||||
201,
|
||||
content=b'{"client_id": "test_client_id", "client_secret": "test_client_secret", "redirect_uris": ["http://localhost:3030/callback"]}',
|
||||
request=registration_request,
|
||||
|
|
@ -1332,7 +1332,7 @@ class TestAuthFlow:
|
|||
assert "code=test_auth_code" in token_request.content.decode()
|
||||
|
||||
# Send a successful token response with 201 status code (test both 200 and 201 are accepted)
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
201,
|
||||
content=(
|
||||
b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600, '
|
||||
|
|
@ -1348,7 +1348,7 @@ class TestAuthFlow:
|
|||
assert str(final_request.url) == "https://api.example.com/mcp"
|
||||
|
||||
# Send final success response to properly close the generator
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -1405,14 +1405,14 @@ class TestAuthFlow:
|
|||
|
||||
oauth_provider.context.callback_handler = mock_callback
|
||||
|
||||
test_request = httpx.Request("GET", "https://api.example.com/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/mcp")
|
||||
auth_flow = oauth_provider.async_auth_flow(test_request)
|
||||
|
||||
# First request
|
||||
request = await auth_flow.__anext__()
|
||||
|
||||
# Send 403 with new scope requirement
|
||||
response_403 = httpx.Response(
|
||||
response_403 = httpx2.Response(
|
||||
403,
|
||||
headers={"WWW-Authenticate": 'Bearer error="insufficient_scope", scope="admin:write admin:delete"'},
|
||||
request=request,
|
||||
|
|
@ -1426,7 +1426,7 @@ class TestAuthFlow:
|
|||
assert redirect_captured
|
||||
|
||||
# Complete the flow with successful token response
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
json={
|
||||
"access_token": "new_token_with_new_scope",
|
||||
|
|
@ -1441,7 +1441,7 @@ class TestAuthFlow:
|
|||
final_request = await auth_flow.asend(token_response)
|
||||
|
||||
# Send success response - flow should complete
|
||||
success_response = httpx.Response(200, request=final_request)
|
||||
success_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(success_response)
|
||||
pytest.fail("Should have stopped after successful response") # pragma: no cover
|
||||
|
|
@ -1485,9 +1485,9 @@ async def test_403_step_up_preserves_scope_from_stored_token(
|
|||
oauth_provider.context.redirect_handler = capture_redirect
|
||||
oauth_provider.context.callback_handler = mock_callback
|
||||
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx.Request("GET", "https://api.example.com/mcp"))
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx2.Request("GET", "https://api.example.com/mcp"))
|
||||
request = await auth_flow.__anext__()
|
||||
response_403 = httpx.Response(
|
||||
response_403 = httpx2.Response(
|
||||
403,
|
||||
headers={"WWW-Authenticate": 'Bearer error="insufficient_scope", scope="write"'},
|
||||
request=request,
|
||||
|
|
@ -1497,14 +1497,14 @@ async def test_403_step_up_preserves_scope_from_stored_token(
|
|||
assert reauthorize_scope == "read write"
|
||||
|
||||
# Drive the flow to completion so the context lock is released cleanly
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
json={"access_token": "new", "token_type": "Bearer", "expires_in": 3600, "scope": "read write"},
|
||||
request=token_exchange_request,
|
||||
)
|
||||
final_request = await auth_flow.asend(token_response)
|
||||
try:
|
||||
await auth_flow.asend(httpx.Response(200, request=final_request))
|
||||
await auth_flow.asend(httpx2.Response(200, request=final_request))
|
||||
except StopAsyncIteration:
|
||||
pass
|
||||
|
||||
|
|
@ -1619,7 +1619,7 @@ class TestLegacyServerFallback:
|
|||
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
|
||||
)
|
||||
|
||||
test_request = httpx.Request("GET", "https://mcp.linear.app/sse")
|
||||
test_request = httpx2.Request("GET", "https://mcp.linear.app/sse")
|
||||
auth_flow = provider.async_auth_flow(test_request)
|
||||
|
||||
# First request
|
||||
|
|
@ -1627,21 +1627,21 @@ class TestLegacyServerFallback:
|
|||
assert "Authorization" not in request.headers
|
||||
|
||||
# Send 401 without WWW-Authenticate header (typical legacy server)
|
||||
response = httpx.Response(401, headers={}, request=test_request)
|
||||
response = httpx2.Response(401, headers={}, request=test_request)
|
||||
|
||||
# Should try path-based PRM first
|
||||
prm_request_1 = await auth_flow.asend(response)
|
||||
assert str(prm_request_1.url) == "https://mcp.linear.app/.well-known/oauth-protected-resource/sse"
|
||||
|
||||
# PRM returns 404
|
||||
prm_response_1 = httpx.Response(404, request=prm_request_1)
|
||||
prm_response_1 = httpx2.Response(404, request=prm_request_1)
|
||||
|
||||
# Should try root-based PRM
|
||||
prm_request_2 = await auth_flow.asend(prm_response_1)
|
||||
assert str(prm_request_2.url) == "https://mcp.linear.app/.well-known/oauth-protected-resource"
|
||||
|
||||
# PRM returns 404 again - all PRM URLs failed
|
||||
prm_response_2 = httpx.Response(404, request=prm_request_2)
|
||||
prm_response_2 = httpx2.Response(404, request=prm_request_2)
|
||||
|
||||
# Should fall back to root OAuth discovery (March 2025 spec behavior)
|
||||
oauth_metadata_request = await auth_flow.asend(prm_response_2)
|
||||
|
|
@ -1649,7 +1649,7 @@ class TestLegacyServerFallback:
|
|||
assert oauth_metadata_request.method == "GET"
|
||||
|
||||
# Send successful OAuth metadata response
|
||||
oauth_metadata_response = httpx.Response(
|
||||
oauth_metadata_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://mcp.linear.app", '
|
||||
|
|
@ -1669,7 +1669,7 @@ class TestLegacyServerFallback:
|
|||
assert str(token_request.url) == "https://mcp.linear.app/token"
|
||||
|
||||
# Send successful token response
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"access_token": "linear_token", "token_type": "Bearer", "expires_in": 3600}',
|
||||
request=token_request,
|
||||
|
|
@ -1681,7 +1681,7 @@ class TestLegacyServerFallback:
|
|||
assert str(final_request.url) == "https://mcp.linear.app/sse"
|
||||
|
||||
# Complete flow
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -1716,13 +1716,13 @@ class TestLegacyServerFallback:
|
|||
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
|
||||
)
|
||||
|
||||
test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
auth_flow = provider.async_auth_flow(test_request)
|
||||
|
||||
await auth_flow.__anext__()
|
||||
|
||||
# 401 with custom WWW-Authenticate PRM URL
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
401,
|
||||
headers={
|
||||
"WWW-Authenticate": 'Bearer resource_metadata="https://custom.prm.com/.well-known/oauth-protected-resource"'
|
||||
|
|
@ -1735,28 +1735,28 @@ class TestLegacyServerFallback:
|
|||
assert str(prm_request_1.url) == "https://custom.prm.com/.well-known/oauth-protected-resource"
|
||||
|
||||
# Returns 500
|
||||
prm_response_1 = httpx.Response(500, request=prm_request_1)
|
||||
prm_response_1 = httpx2.Response(500, request=prm_request_1)
|
||||
|
||||
# Try path-based fallback
|
||||
prm_request_2 = await auth_flow.asend(prm_response_1)
|
||||
assert str(prm_request_2.url) == "https://api.example.com/.well-known/oauth-protected-resource/v1/mcp"
|
||||
|
||||
# Returns 404
|
||||
prm_response_2 = httpx.Response(404, request=prm_request_2)
|
||||
prm_response_2 = httpx2.Response(404, request=prm_request_2)
|
||||
|
||||
# Try root fallback
|
||||
prm_request_3 = await auth_flow.asend(prm_response_2)
|
||||
assert str(prm_request_3.url) == "https://api.example.com/.well-known/oauth-protected-resource"
|
||||
|
||||
# Also returns 404 - all PRM URLs failed
|
||||
prm_response_3 = httpx.Response(404, request=prm_request_3)
|
||||
prm_response_3 = httpx2.Response(404, request=prm_request_3)
|
||||
|
||||
# Should fall back to root OAuth discovery
|
||||
oauth_metadata_request = await auth_flow.asend(prm_response_3)
|
||||
assert str(oauth_metadata_request.url) == "https://api.example.com/.well-known/oauth-authorization-server"
|
||||
|
||||
# Complete the flow
|
||||
oauth_metadata_response = httpx.Response(
|
||||
oauth_metadata_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://api.example.com", '
|
||||
|
|
@ -1773,7 +1773,7 @@ class TestLegacyServerFallback:
|
|||
token_request = await auth_flow.asend(oauth_metadata_response)
|
||||
assert str(token_request.url) == "https://api.example.com/token"
|
||||
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"access_token": "test_token", "token_type": "Bearer", "expires_in": 3600}',
|
||||
request=token_request,
|
||||
|
|
@ -1782,7 +1782,7 @@ class TestLegacyServerFallback:
|
|||
final_request = await auth_flow.asend(token_response)
|
||||
assert final_request.headers["Authorization"] == "Bearer test_token"
|
||||
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -1813,8 +1813,8 @@ class TestSEP985Discovery:
|
|||
)
|
||||
|
||||
# Test with 401 response without WWW-Authenticate header
|
||||
init_response = httpx.Response(
|
||||
status_code=401, headers={}, request=httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
init_response = httpx2.Response(
|
||||
status_code=401, headers={}, request=httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
)
|
||||
|
||||
# Build discovery URLs
|
||||
|
|
@ -1859,7 +1859,7 @@ class TestSEP985Discovery:
|
|||
)
|
||||
|
||||
# Create a test request
|
||||
test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
|
||||
# Mock the auth flow
|
||||
auth_flow = provider.async_auth_flow(test_request)
|
||||
|
|
@ -1869,7 +1869,7 @@ class TestSEP985Discovery:
|
|||
assert "Authorization" not in request.headers
|
||||
|
||||
# Send a 401 response without WWW-Authenticate header
|
||||
response = httpx.Response(401, headers={}, request=test_request)
|
||||
response = httpx2.Response(401, headers={}, request=test_request)
|
||||
|
||||
# Next request should be to discover protected resource metadata (path-based)
|
||||
discovery_request_1 = await auth_flow.asend(response)
|
||||
|
|
@ -1877,7 +1877,7 @@ class TestSEP985Discovery:
|
|||
assert discovery_request_1.method == "GET"
|
||||
|
||||
# Send 404 response for path-based discovery
|
||||
discovery_response_1 = httpx.Response(404, request=discovery_request_1)
|
||||
discovery_response_1 = httpx2.Response(404, request=discovery_request_1)
|
||||
|
||||
# Next request should be to root-based well-known URI
|
||||
discovery_request_2 = await auth_flow.asend(discovery_response_1)
|
||||
|
|
@ -1885,7 +1885,7 @@ class TestSEP985Discovery:
|
|||
assert discovery_request_2.method == "GET"
|
||||
|
||||
# Send successful discovery response
|
||||
discovery_response_2 = httpx.Response(
|
||||
discovery_response_2 = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com"]}'
|
||||
|
|
@ -1901,7 +1901,7 @@ class TestSEP985Discovery:
|
|||
assert oauth_metadata_request.method == "GET"
|
||||
|
||||
# Complete the flow
|
||||
oauth_metadata_response = httpx.Response(
|
||||
oauth_metadata_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://auth.example.com", '
|
||||
|
|
@ -1912,7 +1912,7 @@ class TestSEP985Discovery:
|
|||
)
|
||||
|
||||
token_request = await auth_flow.asend(oauth_metadata_response)
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600, '
|
||||
|
|
@ -1922,7 +1922,7 @@ class TestSEP985Discovery:
|
|||
)
|
||||
|
||||
final_request = await auth_flow.asend(token_response)
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -1949,12 +1949,12 @@ class TestSEP985Discovery:
|
|||
)
|
||||
|
||||
# Test with 401 response with WWW-Authenticate header
|
||||
init_response = httpx.Response(
|
||||
init_response = httpx2.Response(
|
||||
status_code=401,
|
||||
headers={
|
||||
"WWW-Authenticate": 'Bearer resource_metadata="https://custom.example.com/.well-known/oauth-protected-resource"'
|
||||
},
|
||||
request=httpx.Request("GET", "https://api.example.com/v1/mcp"),
|
||||
request=httpx2.Request("GET", "https://api.example.com/v1/mcp"),
|
||||
)
|
||||
|
||||
# Build discovery URLs
|
||||
|
|
@ -2028,10 +2028,10 @@ class TestWWWAuthenticate:
|
|||
):
|
||||
"""Test extraction of various fields from valid WWW-Authenticate headers."""
|
||||
|
||||
init_response = httpx.Response(
|
||||
init_response = httpx2.Response(
|
||||
status_code=401,
|
||||
headers={"WWW-Authenticate": www_auth_header},
|
||||
request=httpx.Request("GET", "https://api.example.com/test"),
|
||||
request=httpx2.Request("GET", "https://api.example.com/test"),
|
||||
)
|
||||
|
||||
result = extract_field_from_www_auth(init_response, field_name)
|
||||
|
|
@ -2063,8 +2063,8 @@ class TestWWWAuthenticate:
|
|||
"""Test extraction returns None for invalid cases."""
|
||||
|
||||
headers = {"WWW-Authenticate": www_auth_header} if www_auth_header is not None else {}
|
||||
init_response = httpx.Response(
|
||||
status_code=401, headers=headers, request=httpx.Request("GET", "https://api.example.com/test")
|
||||
init_response = httpx2.Response(
|
||||
status_code=401, headers=headers, request=httpx2.Request("GET", "https://api.example.com/test")
|
||||
)
|
||||
|
||||
result = extract_field_from_www_auth(init_response, field_name)
|
||||
|
|
@ -2211,7 +2211,7 @@ class TestCIMD:
|
|||
provider.context.token_expiry_time = None
|
||||
provider._initialized = True
|
||||
|
||||
test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
auth_flow = provider.async_auth_flow(test_request)
|
||||
|
||||
# First request
|
||||
|
|
@ -2219,11 +2219,11 @@ class TestCIMD:
|
|||
assert "Authorization" not in request.headers
|
||||
|
||||
# Send 401 response
|
||||
response = httpx.Response(401, headers={}, request=test_request)
|
||||
response = httpx2.Response(401, headers={}, request=test_request)
|
||||
|
||||
# PRM discovery
|
||||
prm_request = await auth_flow.asend(response)
|
||||
prm_response = httpx.Response(
|
||||
prm_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com"]}',
|
||||
request=prm_request,
|
||||
|
|
@ -2231,7 +2231,7 @@ class TestCIMD:
|
|||
|
||||
# OAuth metadata discovery
|
||||
oauth_request = await auth_flow.asend(prm_response)
|
||||
oauth_response = httpx.Response(
|
||||
oauth_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://auth.example.com", '
|
||||
|
|
@ -2262,7 +2262,7 @@ class TestCIMD:
|
|||
assert provider.context.client_info.token_endpoint_auth_method == "none"
|
||||
|
||||
# Complete the flow
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"access_token": "test_token", "token_type": "Bearer", "expires_in": 3600}',
|
||||
request=token_request,
|
||||
|
|
@ -2271,7 +2271,7 @@ class TestCIMD:
|
|||
final_request = await auth_flow.asend(token_response)
|
||||
assert final_request.headers["Authorization"] == "Bearer test_token"
|
||||
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -2302,18 +2302,18 @@ class TestCIMD:
|
|||
provider.context.token_expiry_time = None
|
||||
provider._initialized = True
|
||||
|
||||
test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
auth_flow = provider.async_auth_flow(test_request)
|
||||
|
||||
# First request
|
||||
await auth_flow.__anext__()
|
||||
|
||||
# Send 401 response
|
||||
response = httpx.Response(401, headers={}, request=test_request)
|
||||
response = httpx2.Response(401, headers={}, request=test_request)
|
||||
|
||||
# PRM discovery
|
||||
prm_request = await auth_flow.asend(response)
|
||||
prm_response = httpx.Response(
|
||||
prm_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com"]}',
|
||||
request=prm_request,
|
||||
|
|
@ -2321,7 +2321,7 @@ class TestCIMD:
|
|||
|
||||
# OAuth metadata discovery - server does NOT support CIMD
|
||||
oauth_request = await auth_flow.asend(prm_response)
|
||||
oauth_response = httpx.Response(
|
||||
oauth_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://auth.example.com", '
|
||||
|
|
@ -2338,7 +2338,7 @@ class TestCIMD:
|
|||
assert str(registration_request.url) == "https://auth.example.com/register"
|
||||
|
||||
# Complete the flow to avoid generator cleanup issues
|
||||
registration_response = httpx.Response(
|
||||
registration_response = httpx2.Response(
|
||||
201,
|
||||
content=b'{"client_id": "dcr_client_id", "redirect_uris": ["http://localhost:3030/callback"]}',
|
||||
request=registration_request,
|
||||
|
|
@ -2350,14 +2350,14 @@ class TestCIMD:
|
|||
)
|
||||
|
||||
token_request = await auth_flow.asend(registration_response)
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"access_token": "test_token", "token_type": "Bearer", "expires_in": 3600}',
|
||||
request=token_request,
|
||||
)
|
||||
|
||||
final_request = await auth_flow.asend(token_response)
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -2541,7 +2541,7 @@ class TestSEP2207OfflineAccessScope:
|
|||
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
|
||||
)
|
||||
|
||||
test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
auth_flow = provider.async_auth_flow(test_request)
|
||||
|
||||
# First request
|
||||
|
|
@ -2549,11 +2549,11 @@ class TestSEP2207OfflineAccessScope:
|
|||
assert "Authorization" not in request.headers
|
||||
|
||||
# Send 401
|
||||
response = httpx.Response(401, headers={}, request=test_request)
|
||||
response = httpx2.Response(401, headers={}, request=test_request)
|
||||
|
||||
# PRM discovery
|
||||
prm_request = await auth_flow.asend(response)
|
||||
prm_response = httpx.Response(
|
||||
prm_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"resource": "https://api.example.com/v1/mcp",'
|
||||
|
|
@ -2565,7 +2565,7 @@ class TestSEP2207OfflineAccessScope:
|
|||
|
||||
# OAuth metadata discovery - AS advertises offline_access
|
||||
oauth_request = await auth_flow.asend(prm_response)
|
||||
oauth_response = httpx.Response(
|
||||
oauth_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://auth.example.com",'
|
||||
|
|
@ -2593,7 +2593,7 @@ class TestSEP2207OfflineAccessScope:
|
|||
assert params["prompt"][0] == "consent"
|
||||
|
||||
# Complete the token exchange
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"access_token": "new_access_token", "token_type": "Bearer",'
|
||||
|
|
@ -2606,7 +2606,7 @@ class TestSEP2207OfflineAccessScope:
|
|||
assert final_request.headers["Authorization"] == "Bearer new_access_token"
|
||||
|
||||
# Close the generator
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -2650,18 +2650,18 @@ class TestSEP2207OfflineAccessScope:
|
|||
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
|
||||
)
|
||||
|
||||
test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
|
||||
test_request = httpx2.Request("GET", "https://api.example.com/v1/mcp")
|
||||
auth_flow = provider.async_auth_flow(test_request)
|
||||
|
||||
# First request
|
||||
await auth_flow.__anext__()
|
||||
|
||||
# Send 401
|
||||
response = httpx.Response(401, headers={}, request=test_request)
|
||||
response = httpx2.Response(401, headers={}, request=test_request)
|
||||
|
||||
# PRM discovery
|
||||
prm_request = await auth_flow.asend(response)
|
||||
prm_response = httpx.Response(
|
||||
prm_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"resource": "https://api.example.com/v1/mcp",'
|
||||
|
|
@ -2673,7 +2673,7 @@ class TestSEP2207OfflineAccessScope:
|
|||
|
||||
# OAuth metadata discovery - AS does NOT advertise offline_access
|
||||
oauth_request = await auth_flow.asend(prm_response)
|
||||
oauth_response = httpx.Response(
|
||||
oauth_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://auth.example.com",'
|
||||
|
|
@ -2701,7 +2701,7 @@ class TestSEP2207OfflineAccessScope:
|
|||
assert "prompt" not in params
|
||||
|
||||
# Complete the token exchange
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200,
|
||||
content=b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600}',
|
||||
request=token_request,
|
||||
|
|
@ -2711,7 +2711,7 @@ class TestSEP2207OfflineAccessScope:
|
|||
assert final_request.headers["Authorization"] == "Bearer new_access_token"
|
||||
|
||||
# Close the generator
|
||||
final_response = httpx.Response(200, request=final_request)
|
||||
final_response = httpx2.Response(200, request=final_request)
|
||||
try:
|
||||
await auth_flow.asend(final_response)
|
||||
except StopAsyncIteration:
|
||||
|
|
@ -2846,10 +2846,10 @@ async def test_handle_token_response_backfills_omitted_scope_from_request(
|
|||
has reverted to its constructor value.
|
||||
"""
|
||||
oauth_provider.context.client_metadata.scope = "read admin"
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
200,
|
||||
json={"access_token": "t", "token_type": "Bearer", "expires_in": 3600},
|
||||
request=httpx.Request("POST", "https://auth.example.com/token"),
|
||||
request=httpx2.Request("POST", "https://auth.example.com/token"),
|
||||
)
|
||||
await oauth_provider._handle_token_response(response)
|
||||
|
||||
|
|
@ -2862,10 +2862,10 @@ async def test_handle_token_response_backfills_omitted_scope_from_request(
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_handle_token_response_raises_on_non_2xx_with_body(oauth_provider: OAuthClientProvider):
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
400,
|
||||
json={"error": "invalid_grant"},
|
||||
request=httpx.Request("POST", "https://auth.example.com/token"),
|
||||
request=httpx2.Request("POST", "https://auth.example.com/token"),
|
||||
)
|
||||
with pytest.raises(OAuthTokenError, match=r"Token exchange failed \(400\).*invalid_grant"):
|
||||
await oauth_provider._handle_token_response(response)
|
||||
|
|
@ -2884,10 +2884,10 @@ async def test_handle_refresh_response_carries_prior_scope_and_refresh_token_whe
|
|||
oauth_provider.context.current_tokens = OAuthToken(
|
||||
access_token="old", scope="read write", refresh_token="prior-refresh"
|
||||
)
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
200,
|
||||
json={"access_token": "new", "token_type": "Bearer", "expires_in": 3600},
|
||||
request=httpx.Request("POST", "https://auth.example.com/token"),
|
||||
request=httpx2.Request("POST", "https://auth.example.com/token"),
|
||||
)
|
||||
ok = await oauth_provider._handle_refresh_response(response)
|
||||
|
||||
|
|
@ -2910,10 +2910,10 @@ async def test_handle_refresh_response_adopts_rotated_refresh_token_when_returne
|
|||
oauth_provider.context.current_tokens = OAuthToken(
|
||||
access_token="old", scope="read write", refresh_token="prior-refresh"
|
||||
)
|
||||
response = httpx.Response(
|
||||
response = httpx2.Response(
|
||||
200,
|
||||
json={"access_token": "new", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "rotated"},
|
||||
request=httpx.Request("POST", "https://auth.example.com/token"),
|
||||
request=httpx2.Request("POST", "https://auth.example.com/token"),
|
||||
)
|
||||
ok = await oauth_provider._handle_refresh_response(response)
|
||||
|
||||
|
|
@ -2943,20 +2943,20 @@ async def test_issuer_binding_re_evaluated_after_asm_when_prm_discovery_failed(
|
|||
issuer="https://old-as.example.com",
|
||||
)
|
||||
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx.Request("GET", "https://api.example.com/v1/mcp"))
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx2.Request("GET", "https://api.example.com/v1/mcp"))
|
||||
request = await auth_flow.__anext__()
|
||||
response_401 = httpx.Response(401, request=request)
|
||||
response_401 = httpx2.Response(401, request=request)
|
||||
|
||||
# PRM discovery: path-based then root, both 404.
|
||||
prm_req = await auth_flow.asend(response_401)
|
||||
assert str(prm_req.url) == "https://api.example.com/.well-known/oauth-protected-resource/v1/mcp"
|
||||
prm_req = await auth_flow.asend(httpx.Response(404, request=prm_req))
|
||||
prm_req = await auth_flow.asend(httpx2.Response(404, request=prm_req))
|
||||
assert str(prm_req.url) == "https://api.example.com/.well-known/oauth-protected-resource"
|
||||
|
||||
# ASM discovery via root fallback (no auth_server_url) succeeds with a different issuer.
|
||||
asm_req = await auth_flow.asend(httpx.Response(404, request=prm_req))
|
||||
asm_req = await auth_flow.asend(httpx2.Response(404, request=prm_req))
|
||||
assert str(asm_req.url) == "https://api.example.com/.well-known/oauth-authorization-server"
|
||||
asm_response = httpx.Response(
|
||||
asm_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://api.example.com", '
|
||||
|
|
@ -2981,12 +2981,12 @@ async def test_issuer_binding_re_evaluated_after_asm_when_prm_discovery_failed(
|
|||
"asm_responses",
|
||||
[
|
||||
pytest.param(
|
||||
[httpx.Response(404), httpx.Response(404)],
|
||||
[httpx2.Response(404), httpx2.Response(404)],
|
||||
id="asm-discovery-failed",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
httpx.Response(
|
||||
httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://new-as.example.com", '
|
||||
|
|
@ -3000,7 +3000,7 @@ async def test_issuer_binding_re_evaluated_after_asm_when_prm_discovery_failed(
|
|||
],
|
||||
)
|
||||
async def test_issuer_is_not_stamped_when_registration_falls_back_to_the_resource_origin(
|
||||
oauth_provider: OAuthClientProvider, mock_storage: MockTokenStorage, asm_responses: list[httpx.Response]
|
||||
oauth_provider: OAuthClientProvider, mock_storage: MockTokenStorage, asm_responses: list[httpx2.Response]
|
||||
):
|
||||
"""SEP-2352: a fallback registration is not recorded as bound to the PRM-advertised AS.
|
||||
|
||||
|
|
@ -3032,9 +3032,9 @@ async def test_issuer_is_not_stamped_when_registration_falls_back_to_the_resourc
|
|||
oauth_provider.context.redirect_handler = capture_redirect
|
||||
oauth_provider.context.callback_handler = echo_callback
|
||||
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx.Request("GET", "https://api.example.com/v1/mcp"))
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx2.Request("GET", "https://api.example.com/v1/mcp"))
|
||||
request = await auth_flow.__anext__()
|
||||
response_401 = httpx.Response(
|
||||
response_401 = httpx2.Response(
|
||||
401,
|
||||
headers={
|
||||
"WWW-Authenticate": (
|
||||
|
|
@ -3047,7 +3047,7 @@ async def test_issuer_is_not_stamped_when_registration_falls_back_to_the_resourc
|
|||
# PRM succeeds and advertises a new AS — the discard block fires.
|
||||
prm_req = await auth_flow.asend(response_401)
|
||||
assert str(prm_req.url) == "https://api.example.com/.well-known/oauth-protected-resource"
|
||||
prm_response = httpx.Response(
|
||||
prm_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://new-as.example.com"]}'
|
||||
|
|
@ -3069,7 +3069,7 @@ async def test_issuer_is_not_stamped_when_registration_falls_back_to_the_resourc
|
|||
dcr_req = next_req
|
||||
assert dcr_req.method == "POST"
|
||||
assert str(dcr_req.url) == "https://api.example.com/register"
|
||||
dcr_response = httpx.Response(
|
||||
dcr_response = httpx2.Response(
|
||||
201,
|
||||
json={"client_id": "fallback-client", "redirect_uris": ["http://localhost:3030/callback"]},
|
||||
request=dcr_req,
|
||||
|
|
@ -3083,12 +3083,12 @@ async def test_issuer_is_not_stamped_when_registration_falls_back_to_the_resourc
|
|||
assert stored.issuer is None
|
||||
|
||||
# Drive the flow to completion so the context lock is released cleanly.
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200, json={"access_token": "t", "token_type": "Bearer", "expires_in": 3600}, request=token_req
|
||||
)
|
||||
final_req = await auth_flow.asend(token_response)
|
||||
try:
|
||||
await auth_flow.asend(httpx.Response(200, request=final_req))
|
||||
await auth_flow.asend(httpx2.Response(200, request=final_req))
|
||||
except StopAsyncIteration:
|
||||
pass
|
||||
|
||||
|
|
@ -3121,19 +3121,19 @@ async def test_issuer_is_stamped_when_same_origin_fallback_register_is_on_the_di
|
|||
oauth_provider.context.redirect_handler = capture_redirect
|
||||
oauth_provider.context.callback_handler = echo_callback
|
||||
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx.Request("GET", "https://api.example.com/v1/mcp"))
|
||||
auth_flow = oauth_provider.async_auth_flow(httpx2.Request("GET", "https://api.example.com/v1/mcp"))
|
||||
request = await auth_flow.__anext__()
|
||||
|
||||
# PRM discovery 404s on both well-known URLs.
|
||||
prm_req = await auth_flow.asend(httpx.Response(401, request=request))
|
||||
prm_req = await auth_flow.asend(httpx2.Response(401, request=request))
|
||||
assert str(prm_req.url) == "https://api.example.com/.well-known/oauth-protected-resource/v1/mcp"
|
||||
prm_req = await auth_flow.asend(httpx.Response(404, request=prm_req))
|
||||
prm_req = await auth_flow.asend(httpx2.Response(404, request=prm_req))
|
||||
assert str(prm_req.url) == "https://api.example.com/.well-known/oauth-protected-resource"
|
||||
|
||||
# Root ASM discovery succeeds with the resource origin as issuer and no registration_endpoint.
|
||||
asm_req = await auth_flow.asend(httpx.Response(404, request=prm_req))
|
||||
asm_req = await auth_flow.asend(httpx2.Response(404, request=prm_req))
|
||||
assert str(asm_req.url) == "https://api.example.com/.well-known/oauth-authorization-server"
|
||||
asm_response = httpx.Response(
|
||||
asm_response = httpx2.Response(
|
||||
200,
|
||||
content=(
|
||||
b'{"issuer": "https://api.example.com", '
|
||||
|
|
@ -3147,7 +3147,7 @@ async def test_issuer_is_stamped_when_same_origin_fallback_register_is_on_the_di
|
|||
dcr_req = await auth_flow.asend(asm_response)
|
||||
assert dcr_req.method == "POST"
|
||||
assert str(dcr_req.url) == "https://api.example.com/register"
|
||||
dcr_response = httpx.Response(
|
||||
dcr_response = httpx2.Response(
|
||||
201,
|
||||
json={"client_id": "embedded-client", "redirect_uris": ["http://localhost:3030/callback"]},
|
||||
request=dcr_req,
|
||||
|
|
@ -3161,11 +3161,11 @@ async def test_issuer_is_stamped_when_same_origin_fallback_register_is_on_the_di
|
|||
assert stored.issuer == str(oauth_provider.context.oauth_metadata.issuer)
|
||||
assert urlparse(stored.issuer).netloc == "api.example.com"
|
||||
|
||||
token_response = httpx.Response(
|
||||
token_response = httpx2.Response(
|
||||
200, json={"access_token": "t", "token_type": "Bearer", "expires_in": 3600}, request=token_req
|
||||
)
|
||||
final_req = await auth_flow.asend(token_response)
|
||||
try:
|
||||
await auth_flow.asend(httpx.Response(200, request=final_req))
|
||||
await auth_flow.asend(httpx2.Response(200, request=final_req))
|
||||
except StopAsyncIteration:
|
||||
pass
|
||||
|
|
|
|||
Loading…
Reference in a new issue