content: """
  Tests for refactored OAuth client authentication implementation.
  """
  
  import base64
  import time
  from unittest import mock
  from urllib.parse import unquote
  
  import httpx
  import pytest
  from inline_snapshot import Is, snapshot
  from pydantic import AnyHttpUrl, AnyUrl
  
  from mcp.client.auth import OAuthClientProvider, PKCEParameters
  from mcp.client.auth.utils import (
      build_oauth_authorization_server_metadata_discovery_urls,
      build_protected_resource_metadata_discovery_urls,
      create_client_info_from_metadata_url,
      create_client_registration_request,
      create_oauth_metadata_request,
      extract_field_from_www_auth,
      extract_resource_metadata_from_www_auth,
      extract_scope_from_www_auth,
      get_client_metadata_scopes,
      handle_registration_response,
      is_valid_client_metadata_url,
      should_use_client_metadata_url,
  )
  from mcp.shared.auth import (
      OAuthClientInformationFull,
      OAuthClientMetadata,
      OAuthMetadata,
      OAuthToken,
      ProtectedResourceMetadata,
  )
  
  
  class MockTokenStorage:
      """Mock token storage for testing."""
  
      def __init__(self):
          self._tokens: OAuthToken | None = None
          self._client_info: OAuthClientInformationFull | None = None
  
      async def get_tokens(self) -> OAuthToken | None:
          return self._tokens  # pragma: no cover
  
      async def set_tokens(self, tokens: OAuthToken) -> None:
          self._tokens = tokens
  
      async def get_client_info(self) -> OAuthClientInformationFull | None:
          return self._client_info  # pragma: no cover
  
      async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
          self._client_info = client_info
  
  
  @pytest.fixture
  def mock_storage():
      return MockTokenStorage()
  
  
  @pytest.fixture
  def client_metadata():
      return OAuthClientMetadata(
          client_name="Test Client",
          client_uri=AnyHttpUrl("https://example.com"),
          redirect_uris=[AnyUrl("http://localhost:3030/callback")],
          scope="read write",
      )
  
  
  @pytest.fixture
  def valid_tokens():
      return OAuthToken(
          access_token="test_access_token",
          token_type="Bearer",
          expires_in=3600,
          refresh_token="test_refresh_token",
          scope="read write",
      )
  
  
  @pytest.fixture
  def oauth_provider(client_metadata: OAuthClientMetadata, mock_storage: MockTokenStorage):
      async def redirect_handler(url: str) -> None:
          """Mock redirect handler."""
          pass  # pragma: no cover
  
      async def callback_handler() -> tuple[str, str | None]:
          """Mock callback handler."""
          return "test_auth_code", "test_state"  # pragma: no cover
  
      return OAuthClientProvider(
          server_url="https://api.example.com/v1/mcp",
          client_metadata=client_metadata,
          storage=mock_storage,
          redirect_handler=redirect_handler,
          callback_handler=callback_handler,
      )
  
  
  @pytest.fixture
  def prm_metadata_response():
      """PRM metadata response with scopes."""
      return httpx.Response(
          200,
          content=(
              b'{"resource": "https://api.example.com/v1/mcp", '
              b'"authorization_servers": ["https://auth.example.com"], '
              b'"scopes_supported": ["resource:read", "resource:write"]}'
          ),
      )
  
  
  @pytest.fixture
  def prm_metadata_without_scopes_response():
      """PRM metadata response without scopes."""
      return httpx.Response(
          200,
          content=(
              b'{"resource": "https://api.example.com/v1/mcp", '
              b'"authorization_servers": ["https://auth.example.com"], '
              b'"scopes_supported": null}'
          ),
      )
  
  
  @pytest.fixture
  def init_response_with_www_auth_scope():
      """Initial 401 response with WWW-Authenticate header containing scope."""
      return httpx.Response(
          401,
          headers={"WWW-Authenticate": 'Bearer scope="special:scope from:www-authenticate"'},
          request=httpx.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(
          401,
          headers={},
          request=httpx.Request("GET", "https://api.example.com/test"),
      )
  
  
  class TestPKCEParameters:
      """Test PKCE parameter generation."""
  
      def test_pkce_generation(self):
          """Test PKCE parameter generation creates valid values."""
          pkce = PKCEParameters.generate()
  
          # Verify lengths
          assert len(pkce.code_verifier) == 128
          assert 43 <= len(pkce.code_challenge) <= 128
  
          # Verify characters used in verifier
          allowed_chars = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
          assert all(c in allowed_chars for c in pkce.code_verifier)
  
          # Verify base64url encoding in challenge (no padding)
          assert "=" not in pkce.code_challenge
  
      def test_pkce_uniqueness(self):
          """Test PKCE generates unique values each time."""
          pkce1 = PKCEParameters.generate()
          pkce2 = PKCEParameters.generate()
  
          assert pkce1.code_verifier != pkce2.code_verifier
          assert pkce1.code_challenge != pkce2.code_challenge
  
  
  class TestOAuthContext:
      """Test OAuth context functionality."""
  
      @pytest.mark.anyio
      async def test_oauth_provider_initialization(
          self, oauth_provider: OAuthClientProvider, client_metadata: OAuthClientMetadata, mock_storage: MockTokenStorage
      ):
          """Test OAuthClientProvider basic setup."""
          assert oauth_provider.context.server_url == "https://api.example.com/v1/mcp"
          assert oauth_provider.context.client_metadata == client_metadata
          assert oauth_provider.context.storage == mock_storage
          assert oauth_provider.context.timeout == 300.0
          assert oauth_provider.context is not None
  
      def test_context_url_parsing(self, oauth_provider: OAuthClientProvider):
          """Test get_authorization_base_url() extracts base URLs correctly."""
          context = oauth_provider.context
  
          # Test with path
          assert context.get_authorization_base_url("https://api.example.com/v1/mcp") == "https://api.example.com"
  
          # Test with no path
          assert context.get_authorization_base_url("https://api.example.com") == "https://api.example.com"
  
          # Test with port
          assert (
              context.get_authorization_base_url("https://api.example.com:8080/path/to/mcp")
              == "https://api.example.com:8080"
          )
  
          # Test with query params
          assert (
              context.get_authorization_base_url("https://api.example.com/path?param=value") == "https://api.example.com"
          )
  
      @pytest.mark.anyio
      async def test_token_validity_checking(self, oauth_provider: OAuthClientProvider, valid_tokens: OAuthToken):
          """Test is_token_valid() and can_refresh_token() logic."""
          context = oauth_provider.context
  
          # No tokens - should be invalid
          assert not context.is_token_valid()
          assert not context.can_refresh_token()
  
          # Set valid tokens and client info
          context.current_tokens = valid_tokens
          context.token_expiry_time = time.time() + 1800  # 30 minutes from now
          context.client_info = OAuthClientInformationFull(
              client_id="test_client_id",
              client_secret="test_client_secret",
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
          )
  
          # Should be valid
          assert context.is_token_valid()
          assert context.can_refresh_token()  # Has refresh token and client info
  
          # Expire the token
          context.token_expiry_time = time.time() - 100  # Expired 100 seconds ago
          assert not context.is_token_valid()
          assert context.can_refresh_token()  # Can still refresh
  
          # Remove refresh token
          context.current_tokens.refresh_token = None
          assert not context.can_refresh_token()
  
          # Remove client info
          context.current_tokens.refresh_token = "test_refresh_token"
          context.client_info = None
          assert not context.can_refresh_token()
  
      def test_clear_tokens(self, oauth_provider: OAuthClientProvider, valid_tokens: OAuthToken):
          """Test clear_tokens() removes token data."""
          context = oauth_provider.context
          context.current_tokens = valid_tokens
          context.token_expiry_time = time.time() + 1800
  
          # Clear tokens
          context.clear_tokens()
  
          # Verify cleared
          assert context.current_tokens is None
          assert context.token_expiry_time is None
  
  
  class TestOAuthFlow:
      """Test OAuth flow methods."""
  
      @pytest.mark.anyio
      async def test_build_protected_resource_discovery_urls(
          self, client_metadata: OAuthClientMetadata, mock_storage: MockTokenStorage
      ):
          """Test protected resource metadata discovery URL building with fallback."""
  
          async def redirect_handler(url: str) -> None:
              pass  # pragma: no cover
  
          async def callback_handler() -> tuple[str, str | None]:
              return "test_auth_code", "test_state"  # pragma: no cover
  
          provider = OAuthClientProvider(
              server_url="https://api.example.com",
              client_metadata=client_metadata,
              storage=mock_storage,
              redirect_handler=redirect_handler,
              callback_handler=callback_handler,
          )
  
          # Test without WWW-Authenticate (fallback)
          init_response = httpx.Response(
              status_code=401, headers={}, request=httpx.Request("GET", "https://request-api.example.com")
          )
  
          urls = build_protected_resource_metadata_discovery_urls(
              extract_resource_metadata_from_www_auth(init_response), provider.context.server_url
          )
          assert len(urls) == 1
          assert urls[0] == "https://api.example.com/.well-known/oauth-protected-resource"
  
          # Test with WWW-Authenticate header
          init_response.headers["WWW-Authenticate"] = (
              'Bearer resource_metadata="https://prm.example.com/.well-known/oauth-protected-resource/path"'
          )
  
          urls = build_protected_resource_metadata_discovery_urls(
              extract_resource_metadata_from_www_auth(init_response), provider.context.server_url
          )
          assert len(urls) == 2
          assert urls[0] == "https://prm.example.com/.well-known/oauth-protected-resource/path"
          assert urls[1] == "https://api.example.com/.well-known/oauth-protected-resource"
  
      @pytest.mark.anyio
      def test_create_oauth_metadata_request(self, oauth_provider: OAuthClientProvider):
          """Test OAuth metadata discovery request building."""
          request = create_oauth_metadata_request("https://example.com")
  
          # Ensure correct method and headers, and that the URL is unmodified
          assert request.method == "GET"
          assert str(request.url) == "https://example.com"
          assert "mcp-protocol-version" in request.headers
  
  
  class TestOAuthFallback:
      """Test OAuth discovery fallback behavior for legacy (act as AS not RS) servers."""
  
      @pytest.mark.anyio
      async def test_oauth_discovery_legacy_fallback_when_no_prm(self):
          """Test that when PRM discovery fails, only root OAuth URL is tried (March 2025 spec)."""
          # When auth_server_url is None (PRM failed), we use server_url and only try root
          discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(None, "https://mcp.linear.app/sse")
  
          # Should only try the root URL (legacy behavior)
          assert discovery_urls == [
              "https://mcp.linear.app/.well-known/oauth-authorization-server",
          ]
  
      @pytest.mark.anyio
      async def test_oauth_discovery_path_aware_when_auth_server_has_path(self):
          """Test that when auth server URL has a path, only path-based URLs are tried."""
          discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(
              "https://auth.example.com/tenant1", "https://api.example.com/mcp"
          )
  
          # Should try path-based URLs only (no root URLs)
          assert discovery_urls == [
              "https://auth.example.com/.well-known/oauth-authorization-server/tenant1",
              "https://auth.example.com/.well-known/openid-configuration/tenant1",
              "https://auth.example.com/tenant1/.well-known/openid-configuration",
          ]
  
      @pytest.mark.anyio
      async def test_oauth_discovery_root_when_auth_server_has_no_path(self):
          """Test that when auth server URL has no path, only root URLs are tried."""
          discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(
              "https://auth.example.com", "https://api.example.com/mcp"
          )
  
          # Should try root URLs only
          assert discovery_urls == [
              "https://auth.example.com/.well-known/oauth-authorization-server",
              "https://auth.example.com/.well-known/openid-configuration",
          ]
  
      @pytest.mark.anyio
      async def test_oauth_discovery_root_when_auth_server_has_only_slash(self):
          """Test that when auth server URL has only trailing slash, treated as root."""
          discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(
              "https://auth.example.com/", "https://api.example.com/mcp"
          )
  
          # Should try root URLs only
          assert discovery_urls == [
              "https://auth.example.com/.well-known/oauth-authorization-server",
              "https://auth.example.com/.well-known/openid-configuration",
          ]
  
      @pytest.mark.anyio
      async def test_oauth_discovery_fallback_order(self, oauth_provider: OAuthClientProvider):
          """Test fallback URL construction order when auth server URL has a path."""
          # Simulate PRM discovery returning an auth server URL with a path
          oauth_provider.context.auth_server_url = oauth_provider.context.server_url
  
          discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(
              oauth_provider.context.auth_server_url, oauth_provider.context.server_url
          )
  
          assert discovery_urls == [
              "https://api.example.com/.well-known/oauth-authorization-server/v1/mcp",
              "https://api.example.com/.well-known/openid-configuration/v1/mcp",
              "https://api.example.com/v1/mcp/.well-known/openid-configuration",
          ]
  
      @pytest.mark.anyio
      async def test_oauth_discovery_fallback_conditions(self, oauth_provider: OAuthClientProvider):
          """Test the conditions during which an AS metadata discovery fallback will be attempted."""
          # Ensure no tokens are stored
          oauth_provider.context.current_tokens = None
          oauth_provider.context.token_expiry_time = None
          oauth_provider._initialized = True
  
          # Mock client info to skip DCR
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id="existing_client",
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
          )
  
          # Create a test request
          test_request = httpx.Request("GET", "https://api.example.com/v1/mcp")
  
          # Mock the auth flow
          auth_flow = oauth_provider.async_auth_flow(test_request)
  
          # First request should be the original request without auth header
          request = await auth_flow.__anext__()
          assert "Authorization" not in request.headers
  
          # Send a 401 response to trigger the OAuth flow
          response = httpx.Response(
              401,
              headers={
                  "WWW-Authenticate": 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"'
              },
              request=test_request,
          )
  
          # Next request should be to discover protected resource metadata
          discovery_request = await auth_flow.asend(response)
          assert str(discovery_request.url) == "https://api.example.com/.well-known/oauth-protected-resource"
          assert discovery_request.method == "GET"
  
          # 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(
              200,
              content=b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com/v1/mcp"]}',
              request=discovery_request,
          )
  
          # Next request should be to discover OAuth metadata at path-aware OAuth URL
          oauth_metadata_request_1 = await auth_flow.asend(discovery_response)
          assert (
              str(oauth_metadata_request_1.url)
              == "https://auth.example.com/.well-known/oauth-authorization-server/v1/mcp"
          )
          assert oauth_metadata_request_1.method == "GET"
  
          # Send a 404 response
          oauth_metadata_response_1 = httpx.Response(
              404,
              content=b"Not Found",
              request=oauth_metadata_request_1,
          )
  
          # Next request should be path-aware OIDC URL (not root URL since auth server has path)
          oauth_metadata_request_2 = await auth_flow.asend(oauth_metadata_response_1)
          assert str(oauth_metadata_request_2.url) == "https://auth.example.com/.well-known/openid-configuration/v1/mcp"
          assert oauth_metadata_request_2.method == "GET"
  
          # Send a 400 response
          oauth_metadata_response_2 = httpx.Response(
              400,
              content=b"Bad Request",
              request=oauth_metadata_request_2,
          )
  
          # Next request should be OIDC path-appended URL
          oauth_metadata_request_3 = await auth_flow.asend(oauth_metadata_response_2)
          assert str(oauth_metadata_request_3.url) == "https://auth.example.com/v1/mcp/.well-known/openid-configuration"
          assert oauth_metadata_request_3.method == "GET"
  
          # Send a 500 response
          oauth_metadata_response_3 = httpx.Response(
              500,
              content=b"Internal Server Error",
              request=oauth_metadata_request_3,
          )
  
          # Mock the authorization process to minimize unnecessary state in this test
          oauth_provider._perform_authorization_code_grant = mock.AsyncMock(
              return_value=("test_auth_code", "test_code_verifier")
          )
  
          # All path-based URLs failed, flow continues with default endpoints
          # Next request should be token exchange using MCP server base URL (fallback when OAuth metadata not found)
          token_request = await auth_flow.asend(oauth_metadata_response_3)
          assert str(token_request.url) == "https://api.example.com/token"
          assert token_request.method == "POST"
  
          # Send a successful token response
          token_response = httpx.Response(
              200,
              content=(
                  b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600, '
                  b'"refresh_token": "new_refresh_token"}'
              ),
              request=token_request,
          )
  
          # After OAuth flow completes, the original request is retried with auth header
          final_request = await auth_flow.asend(token_response)
          assert final_request.headers["Authorization"] == "Bearer new_access_token"
          assert final_request.method == "GET"
          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)
          try:
              await auth_flow.asend(final_response)
          except StopAsyncIteration:
              pass  # Expected - generator should complete
  
      @pytest.mark.anyio
      async def test_handle_metadata_response_success(self, oauth_provider: OAuthClientProvider):
          """Test successful metadata response handling."""
          # Create minimal valid OAuth metadata
          content = b"""{
              "issuer": "https://auth.example.com",
              "authorization_endpoint": "https://auth.example.com/authorize",
              "token_endpoint": "https://auth.example.com/token"
          }"""
          response = httpx.Response(200, content=content)
  
          # Should set metadata
          await oauth_provider._handle_oauth_metadata_response(response)
          assert oauth_provider.context.oauth_metadata is not None
          assert str(oauth_provider.context.oauth_metadata.issuer) == "https://auth.example.com/"
  
      @pytest.mark.anyio
      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,
      ):
          """Test that WWW-Authenticate scope is prioritized over PRM scopes."""
          # First, process PRM metadata to set protected_resource_metadata with scopes
          await oauth_provider._handle_protected_resource_response(prm_metadata_response)
  
          # Process the scope selection with WWW-Authenticate header
          scopes = get_client_metadata_scopes(
              extract_scope_from_www_auth(init_response_with_www_auth_scope),
              oauth_provider.context.protected_resource_metadata,
          )
  
          # Verify that WWW-Authenticate scope is used (not PRM scopes)
          assert scopes == "special:scope from:www-authenticate"
  
      @pytest.mark.anyio
      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,
      ):
          """Test that PRM scopes are prioritized when WWW-Authenticate header has no scopes."""
          # Process the PRM metadata to set protected_resource_metadata with scopes
          await oauth_provider._handle_protected_resource_response(prm_metadata_response)
  
          # Process the scope selection without WWW-Authenticate scope
          scopes = get_client_metadata_scopes(
              extract_scope_from_www_auth(init_response_without_www_auth_scope),
              oauth_provider.context.protected_resource_metadata,
          )
  
          # Verify that PRM scopes are used
          assert scopes == "resource:read resource:write"
  
      @pytest.mark.anyio
      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,
      ):
          """Test that scope is omitted when PRM has no scopes and WWW-Authenticate doesn't specify scope."""
          # Process the PRM metadata without scopes
          await oauth_provider._handle_protected_resource_response(prm_metadata_without_scopes_response)
  
          # Process the scope selection without WWW-Authenticate scope
          scopes = get_client_metadata_scopes(
              extract_scope_from_www_auth(init_response_without_www_auth_scope),
              oauth_provider.context.protected_resource_metadata,
          )
          # Verify that scope is omitted
          assert scopes is None
  
      @pytest.mark.anyio
      async def test_token_exchange_request_authorization_code(self, oauth_provider: OAuthClientProvider):
          """Test token exchange request building."""
          # Set up required context
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id="test_client",
              client_secret="test_secret",
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
              token_endpoint_auth_method="client_secret_post",
          )
  
          request = await oauth_provider._exchange_token_authorization_code("test_auth_code", "test_verifier")
  
          assert request.method == "POST"
          assert str(request.url) == "https://api.example.com/token"
          assert request.headers["Content-Type"] == "application/x-www-form-urlencoded"
  
          # Check form data
          content = request.content.decode()
          assert "grant_type=authorization_code" in content
          assert "code=test_auth_code" in content
          assert "code_verifier=test_verifier" in content
          assert "client_id=test_client" in content
          assert "client_secret=test_secret" in content
  
      @pytest.mark.anyio
      async def test_refresh_token_request(self, oauth_provider: OAuthClientProvider, valid_tokens: OAuthToken):
          """Test refresh token request building."""
          # Set up required context
          oauth_provider.context.current_tokens = valid_tokens
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id="test_client",
              client_secret="test_secret",
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
              token_endpoint_auth_method="client_secret_post",
          )
  
          request = await oauth_provider._refresh_token()
  
          assert request.method == "POST"
          assert str(request.url) == "https://api.example.com/token"
          assert request.headers["Content-Type"] == "application/x-www-form-urlencoded"
  
          # Check form data
          content = request.content.decode()
          assert "grant_type=refresh_token" in content
          assert "refresh_token=test_refresh_token" in content
          assert "client_id=test_client" in content
          assert "client_secret=test_secret" in content
  
      @pytest.mark.anyio
      async def test_basic_auth_token_exchange(self, oauth_provider: OAuthClientProvider):
          """Test token exchange with client_secret_basic authentication."""
          # Set up OAuth metadata to support basic auth
          oauth_provider.context.oauth_metadata = OAuthMetadata(
              issuer=AnyHttpUrl("https://auth.example.com"),
              authorization_endpoint=AnyHttpUrl("https://auth.example.com/authorize"),
              token_endpoint=AnyHttpUrl("https://auth.example.com/token"),
              token_endpoint_auth_methods_supported=["client_secret_basic", "client_secret_post"],
          )
  
          client_id_raw = "test@client"  # Include special character to test URL encoding
          client_secret_raw = "test:secret"  # Include colon to test URL encoding
  
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id=client_id_raw,
              client_secret=client_secret_raw,
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
              token_endpoint_auth_method="client_secret_basic",
          )
  
          request = await oauth_provider._exchange_token_authorization_code("test_auth_code", "test_verifier")
  
          # Should use basic auth (registered method)
          assert "Authorization" in request.headers
          assert request.headers["Authorization"].startswith("Basic ")
  
          # Decode and verify credentials are properly URL-encoded
          encoded_creds = request.headers["Authorization"][6:]  # Remove "Basic " prefix
          decoded = base64.b64decode(encoded_creds).decode()
          client_id, client_secret = decoded.split(":", 1)
  
          # Check URL encoding was applied
          assert client_id == "test%40client"  # @ should be encoded as %40
          assert client_secret == "test%3Asecret"  # : should be encoded as %3A
  
          # Verify decoded values match original
          assert unquote(client_id) == client_id_raw
          assert unquote(client_secret) == client_secret_raw
  
          # client_secret should NOT be in body for basic auth
          content = request.content.decode()
          assert "client_secret=" not in content
          assert "client_id=test%40client" in content  # client_id still in body
  
      @pytest.mark.anyio
      async def test_basic_auth_refresh_token(self, oauth_provider: OAuthClientProvider, valid_tokens: OAuthToken):
          """Test token refresh with client_secret_basic authentication."""
          oauth_provider.context.current_tokens = valid_tokens
  
          # Set up OAuth metadata to only support basic auth
          oauth_provider.context.oauth_metadata = OAuthMetadata(
              issuer=AnyHttpUrl("https://auth.example.com"),
              authorization_endpoint=AnyHttpUrl("https://auth.example.com/authorize"),
              token_endpoint=AnyHttpUrl("https://auth.example.com/token"),
              token_endpoint_auth_methods_supported=["client_secret_basic"],
          )
  
          client_id = "test_client"
          client_secret = "test_secret"
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id=client_id,
              client_secret=client_secret,
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
              token_endpoint_auth_method="client_secret_basic",
          )
  
          request = await oauth_provider._refresh_token()
  
          assert "Authorization" in request.headers
          assert request.headers["Authorization"].startswith("Basic ")
  
          encoded_creds = request.headers["Authorization"][6:]
          decoded = base64.b64decode(encoded_creds).decode()
          assert decoded == f"{client_id}:{client_secret}"
  
          # client_secret should NOT be in body
          content = request.content.decode()
          assert "client_secret=" not in content
  
      @pytest.mark.anyio
      async def test_none_auth_method(self, oauth_provider: OAuthClientProvider):
          """Test 'none' authentication method (public client)."""
          oauth_provider.context.oauth_metadata = OAuthMetadata(
              issuer=AnyHttpUrl("https://auth.example.com"),
              authorization_endpoint=AnyHttpUrl("https://auth.example.com/authorize"),
              token_endpoint=AnyHttpUrl("https://auth.example.com/token"),
              token_endpoint_auth_methods_supported=["none"],
          )
  
          client_id = "public_client"
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id=client_id,
              client_secret=None,  # No secret for public client
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
              token_endpoint_auth_method="none",
          )
  
          request = await oauth_provider._exchange_token_authorization_code("test_auth_code", "test_verifier")
  
          # Should NOT have Authorization header
          assert "Authorization" not in request.headers
  
          # Should NOT have client_secret in body
          content = request.content.decode()
          assert "client_secret=" not in content
          assert "client_id=public_client" in content
  
  
  class TestProtectedResourceMetadata:
      """Test protected resource handling."""
  
      @pytest.mark.anyio
      async def test_resource_param_included_with_recent_protocol_version(self, oauth_provider: OAuthClientProvider):
          """Test resource parameter is included for protocol version >= 2025-06-18."""
          # Set protocol version to 2025-06-18
          oauth_provider.context.protocol_version = "2025-06-18"
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id="test_client",
              client_secret="test_secret",
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
          )
  
          # Test in token exchange
          request = await oauth_provider._exchange_token_authorization_code("test_code", "test_verifier")
          content = request.content.decode()
          assert "resource=" in content
          # Check URL-encoded resource parameter
          from urllib.parse import quote
  
          expected_resource = quote(oauth_provider.context.get_resource_url(), safe="")
          assert f"resource={expected_resource}" in content
  
          # Test in refresh token
          oauth_provider.context.current_tokens = OAuthToken(
              access_token="test_access",
              token_type="Bearer",
              refresh_token="test_refresh",
          )
          refresh_request = await oauth_provider._refresh_token()
          refresh_content = refresh_request.content.decode()
          assert "resource=" in refresh_content
  
      @pytest.mark.anyio
      async def test_resource_param_excluded_with_old_protocol_version(self, oauth_provider: OAuthClientProvider):
          """Test resource parameter is excluded for protocol version < 2025-06-18."""
          # Set protocol version to older version
          oauth_provider.context.protocol_version = "2025-03-26"
          oauth_provider.context.client_info = OAuthClientInformationFull(
              client_id="test_client",
              client_secret="test_secret",
              redirect_uris=[AnyUrl("http://localhost:3030/callback")],
          )
  
          # Test in token exchange
          request = await oauth_provider._exchange_token_authorization_code("test_code", "test_verifier")
          content = request.content.decode()
          assert "resource=" not in content
  
          # Test in refresh token
  