@@ -84,6 +84,41 @@ def oauth_provider_without_scope(oauth_provider: OAuthClientProvider) -> OAuthCl
8484 oauth_provider .context .client_metadata .scope = None
8585 return oauth_provider
8686
87+
88+ @pytest .fixture
89+ def oauth_metadata_response ():
90+ """Common OAuth metadata response with scopes."""
91+ return httpx .Response (
92+ 200 ,
93+ content = (
94+ b'{"issuer": "https://auth.example.com", '
95+ b'"authorization_endpoint": "https://auth.example.com/authorize", '
96+ b'"token_endpoint": "https://auth.example.com/token", '
97+ b'"registration_endpoint": "https://auth.example.com/register", '
98+ b'"scopes_supported": ["read", "write", "admin"]}'
99+ ),
100+ )
101+
102+
103+ @pytest .fixture
104+ def prm_metadata ():
105+ """PRM metadata with scopes."""
106+ return ProtectedResourceMetadata (
107+ resource = AnyHttpUrl ("https://api.example.com/v1/mcp" ),
108+ authorization_servers = [AnyHttpUrl ("https://auth.example.com" )],
109+ scopes_supported = ["resource:read" , "resource:write" ],
110+ )
111+
112+
113+ @pytest .fixture
114+ def prm_metadata_without_scopes ():
115+ """PRM metadata without scopes."""
116+ return ProtectedResourceMetadata (
117+ resource = AnyHttpUrl ("https://api.example.com/v1/mcp" ),
118+ authorization_servers = [AnyHttpUrl ("https://auth.example.com" )],
119+ scopes_supported = None ,
120+ )
121+
87122class TestPKCEParameters :
88123 """Test PKCE parameter generation."""
89124
@@ -397,28 +432,15 @@ async def test_handle_metadata_response_success(self, oauth_provider: OAuthClien
397432 assert str (oauth_provider .context .oauth_metadata .issuer ) == "https://auth.example.com/"
398433
399434 @pytest .mark .anyio
400- async def test_prioritize_prm_scopes_over_oauth_metadata (self , oauth_provider_without_scope : OAuthClientProvider ):
435+ async def test_prioritize_prm_scopes_over_oauth_metadata (
436+ self , oauth_provider_without_scope : OAuthClientProvider ,
437+ oauth_metadata_response : httpx .Response , prm_metadata : ProtectedResourceMetadata
438+ ):
401439 """Test that PRM scopes are prioritized over auth server metadata scopes."""
402440 provider = oauth_provider_without_scope
403441
404442 # Set up PRM metadata with specific scopes
405- provider .context .protected_resource_metadata = ProtectedResourceMetadata (
406- resource = AnyHttpUrl ("https://api.example.com/v1/mcp" ),
407- authorization_servers = [AnyHttpUrl ("https://auth.example.com" )],
408- scopes_supported = ["resource:read" , "resource:write" ],
409- )
410-
411- # Create OAuth metadata response with different scopes
412- oauth_metadata_response = httpx .Response (
413- 200 ,
414- content = (
415- b'{"issuer": "https://auth.example.com", '
416- b'"authorization_endpoint": "https://auth.example.com/authorize", '
417- b'"token_endpoint": "https://auth.example.com/token", '
418- b'"registration_endpoint": "https://auth.example.com/register", '
419- b'"scopes_supported": ["read", "write", "admin"]}'
420- ),
421- )
443+ provider .context .protected_resource_metadata = prm_metadata
422444
423445 # Process the OAuth metadata
424446 await provider ._handle_oauth_metadata_response (oauth_metadata_response )
@@ -428,29 +450,14 @@ async def test_prioritize_prm_scopes_over_oauth_metadata(self, oauth_provider_wi
428450
429451 @pytest .mark .anyio
430452 async def test_fallback_to_oauth_metadata_scopes_when_no_prm_scopes (
431- self , oauth_provider_without_scope : OAuthClientProvider
453+ self , oauth_provider_without_scope : OAuthClientProvider ,
454+ oauth_metadata_response : httpx .Response , prm_metadata_without_scopes : ProtectedResourceMetadata
432455 ):
433456 """Test fallback to OAuth metadata scopes when PRM has no scopes."""
434457 provider = oauth_provider_without_scope
435458
436459 # Set up PRM metadata without scopes
437- provider .context .protected_resource_metadata = ProtectedResourceMetadata (
438- resource = AnyHttpUrl ("https://api.example.com/v1/mcp" ),
439- authorization_servers = [AnyHttpUrl ("https://auth.example.com" )],
440- scopes_supported = None , # No scopes in PRM
441- )
442-
443- # Create OAuth metadata response with scopes
444- oauth_metadata_response = httpx .Response (
445- 200 ,
446- content = (
447- b'{"issuer": "https://auth.example.com", '
448- b'"authorization_endpoint": "https://auth.example.com/authorize", '
449- b'"token_endpoint": "https://auth.example.com/token", '
450- b'"registration_endpoint": "https://auth.example.com/register", '
451- b'"scopes_supported": ["read", "write", "admin"]}'
452- ),
453- )
460+ provider .context .protected_resource_metadata = prm_metadata_without_scopes
454461
455462 # Process the OAuth metadata
456463 await provider ._handle_oauth_metadata_response (oauth_metadata_response )
@@ -459,19 +466,18 @@ async def test_fallback_to_oauth_metadata_scopes_when_no_prm_scopes(
459466 assert provider .context .client_metadata .scope == "read write admin"
460467
461468 @pytest .mark .anyio
462- async def test_no_scope_changes_when_both_missing (self , oauth_provider_without_scope : OAuthClientProvider ):
469+ async def test_no_scope_changes_when_both_missing (
470+ self , oauth_provider_without_scope : OAuthClientProvider ,
471+ prm_metadata_without_scopes : ProtectedResourceMetadata
472+ ):
463473 """Test that no scope changes occur when both PRM and OAuth metadata lack scopes."""
464474 provider = oauth_provider_without_scope
465475
466476 # Set up PRM metadata without scopes
467- provider .context .protected_resource_metadata = ProtectedResourceMetadata (
468- resource = AnyHttpUrl ("https://api.example.com/v1/mcp" ),
469- authorization_servers = [AnyHttpUrl ("https://auth.example.com" )],
470- scopes_supported = None , # No scopes in PRM
471- )
477+ provider .context .protected_resource_metadata = prm_metadata_without_scopes
472478
473479 # Create OAuth metadata response without scopes
474- oauth_metadata_response = httpx .Response (
480+ custom_oauth_metadata_response = httpx .Response (
475481 200 ,
476482 content = (
477483 b'{"issuer": "https://auth.example.com", '
@@ -483,36 +489,21 @@ async def test_no_scope_changes_when_both_missing(self, oauth_provider_without_s
483489 )
484490
485491 # Process the OAuth metadata
486- await provider ._handle_oauth_metadata_response (oauth_metadata_response )
492+ await provider ._handle_oauth_metadata_response (custom_oauth_metadata_response )
487493
488494 # Verify that scope remains None
489495 assert provider .context .client_metadata .scope is None
490496
491497 @pytest .mark .anyio
492498 async def test_preserve_existing_client_scope (
493- self , oauth_provider : OAuthClientProvider
499+ self , oauth_provider : OAuthClientProvider ,
500+ oauth_metadata_response : httpx .Response , prm_metadata : ProtectedResourceMetadata
494501 ):
495502 """Test that existing client scope is preserved regardless of metadata."""
496503 provider = oauth_provider
497504
498505 # Set up PRM metadata with scopes
499- provider .context .protected_resource_metadata = ProtectedResourceMetadata (
500- resource = AnyHttpUrl ("https://api.example.com/v1/mcp" ),
501- authorization_servers = [AnyHttpUrl ("https://auth.example.com" )],
502- scopes_supported = ["resource:read" , "resource:write" ],
503- )
504-
505- # Create OAuth metadata response with scopes
506- oauth_metadata_response = httpx .Response (
507- 200 ,
508- content = (
509- b'{"issuer": "https://auth.example.com", '
510- b'"authorization_endpoint": "https://auth.example.com/authorize", '
511- b'"token_endpoint": "https://auth.example.com/token", '
512- b'"registration_endpoint": "https://auth.example.com/register", '
513- b'"scopes_supported": ["read", "write", "admin"]}'
514- ),
515- )
506+ provider .context .protected_resource_metadata = prm_metadata
516507
517508 # Process the OAuth metadata
518509 await provider ._handle_oauth_metadata_response (oauth_metadata_response )
0 commit comments