@@ -343,7 +343,7 @@ async def test_handle_metadata_response_success(self, oauth_provider):
343343 # Create minimal valid OAuth metadata
344344 content = b"""{
345345 "issuer": "https://auth.example.com",
346- "authorization_endpoint": "https://auth.example.com/authorize",
346+ "authorization_endpoint": "https://auth.example.com/authorize",
347347 "token_endpoint": "https://auth.example.com/token"
348348 }"""
349349 response = httpx .Response (200 , content = content )
@@ -572,6 +572,105 @@ async def test_auth_flow_with_valid_tokens(self, oauth_provider, mock_storage, v
572572 except StopAsyncIteration :
573573 pass # Expected
574574
575+ @pytest .mark .anyio
576+ async def test_auth_flow_with_no_tokens (self , oauth_provider , mock_storage ):
577+ """Test auth flow when no tokens are available, triggering the full OAuth flow."""
578+ # Ensure no tokens are stored
579+ oauth_provider .context .current_tokens = None
580+ oauth_provider .context .token_expiry_time = None
581+ oauth_provider ._initialized = True
582+
583+ # Create a test request
584+ test_request = httpx .Request ("GET" , "https://api.example.com/mcp" )
585+
586+ # Mock the auth flow
587+ auth_flow = oauth_provider .async_auth_flow (test_request )
588+
589+ # First request should be the original request without auth header
590+ request = await auth_flow .__anext__ ()
591+ assert "Authorization" not in request .headers
592+
593+ # Send a 401 response to trigger the OAuth flow
594+ response = httpx .Response (
595+ 401 ,
596+ headers = {
597+ "WWW-Authenticate" : 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"'
598+ },
599+ request = test_request ,
600+ )
601+
602+ # Next request should be to discover protected resource metadata
603+ discovery_request = await auth_flow .asend (response )
604+ assert discovery_request .method == "GET"
605+ assert str (discovery_request .url ) == "https://api.example.com/.well-known/oauth-protected-resource"
606+
607+ # Send a successful discovery response with minimal protected resource metadata
608+ discovery_response = httpx .Response (
609+ 200 ,
610+ content = b'{"resource": "https://api.example.com/mcp", "authorization_servers": ["https://auth.example.com"]}' ,
611+ request = discovery_request ,
612+ )
613+
614+ # Next request should be to discover OAuth metadata
615+ oauth_metadata_request = await auth_flow .asend (discovery_response )
616+ assert oauth_metadata_request .method == "GET"
617+ assert str (oauth_metadata_request .url ).startswith ("https://auth.example.com/" )
618+ assert "mcp-protocol-version" in oauth_metadata_request .headers
619+
620+ # Send a successful OAuth metadata response
621+ oauth_metadata_response = httpx .Response (
622+ 200 ,
623+ content = (
624+ b'{"issuer": "https://auth.example.com", '
625+ b'"authorization_endpoint": "https://auth.example.com/authorize", '
626+ b'"token_endpoint": "https://auth.example.com/token", '
627+ b'"registration_endpoint": "https://auth.example.com/register"}'
628+ ),
629+ request = oauth_metadata_request ,
630+ )
631+
632+ # Next request should be to register client
633+ registration_request = await auth_flow .asend (oauth_metadata_response )
634+ assert registration_request .method == "POST"
635+ assert str (registration_request .url ) == "https://auth.example.com/register"
636+
637+ # Send a successful registration response
638+ registration_response = httpx .Response (
639+ 201 ,
640+ content = b'{"client_id": "test_client_id", "client_secret": "test_client_secret", "redirect_uris": ["http://localhost:3030/callback"]}' ,
641+ request = registration_request ,
642+ )
643+
644+ # Mock the authorization process
645+ oauth_provider ._perform_authorization = mock .AsyncMock (return_value = ("test_auth_code" , "test_code_verifier" ))
646+
647+ # Next request should be to exchange token
648+ token_request = await auth_flow .asend (registration_response )
649+ assert token_request .method == "POST"
650+ assert str (token_request .url ) == "https://auth.example.com/token"
651+ assert "code=test_auth_code" in token_request .content .decode ()
652+
653+ # Send a successful token response
654+ token_response = httpx .Response (
655+ 200 ,
656+ content = (
657+ b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600, '
658+ b'"refresh_token": "new_refresh_token"}'
659+ ),
660+ request = token_request ,
661+ )
662+
663+ # Final request should be the original request with auth header
664+ final_request = await auth_flow .asend (token_response )
665+ assert final_request .headers ["Authorization" ] == "Bearer new_access_token"
666+ assert final_request .method == "GET"
667+ assert str (final_request .url ) == "https://api.example.com/mcp"
668+
669+ # Verify tokens were stored
670+ assert oauth_provider .context .current_tokens is not None
671+ assert oauth_provider .context .current_tokens .access_token == "new_access_token"
672+ assert oauth_provider .context .token_expiry_time is not None
673+
575674
576675class TestClientCredentialsProvider :
577676 @pytest .mark .anyio
0 commit comments