content: """
  Integration tests for MCP authorization components.
  """
  
  import base64
  import hashlib
  import secrets
  import time
  import unittest.mock
  from typing import Any
  from urllib.parse import parse_qs, urlparse
  
  import httpx
  import pytest
  from pydantic import AnyHttpUrl, AnyUrl
  from starlette.applications import Starlette
  
  from mcp.server.auth.provider import (
      AccessToken,
      AuthorizationCode,
      AuthorizationParams,
      OAuthAuthorizationServerProvider,
      RefreshToken,
      construct_redirect_uri,
  )
  from mcp.server.auth.routes import ClientRegistrationOptions, RevocationOptions, create_auth_routes
  from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
  
  
  # Mock OAuth provider for testing
  class MockOAuthProvider(OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken]):
      def __init__(self):
          self.clients: dict[str, OAuthClientInformationFull] = {}
          self.auth_codes: dict[str, AuthorizationCode] = {}  # code -> {client_id, code_challenge, redirect_uri}
          self.tokens: dict[str, AccessToken] = {}  # token -> {client_id, scopes, expires_at}
          self.refresh_tokens: dict[str, str] = {}  # refresh_token -> access_token
  
      async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
          return self.clients.get(client_id)
  
      async def register_client(self, client_info: OAuthClientInformationFull):
          assert client_info.client_id is not None
          self.clients[client_info.client_id] = client_info
  
      async def authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str:
          # toy authorize implementation which just immediately generates an authorization
          # code and completes the redirect
          assert client.client_id is not None
          code = AuthorizationCode(
              code=f"code_{int(time.time())}",
              client_id=client.client_id,
              code_challenge=params.code_challenge,
              redirect_uri=params.redirect_uri,
              redirect_uri_provided_explicitly=params.redirect_uri_provided_explicitly,
              expires_at=time.time() + 300,
              scopes=params.scopes or ["read", "write"],
          )
          self.auth_codes[code.code] = code
  
          return construct_redirect_uri(str(params.redirect_uri), code=code.code, state=params.state)
  
      async def load_authorization_code(
          self, client: OAuthClientInformationFull, authorization_code: str
      ) -> AuthorizationCode | None:
          return self.auth_codes.get(authorization_code)
  
      async def exchange_authorization_code(
          self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode
      ) -> OAuthToken:
          assert authorization_code.code in self.auth_codes
  
          # Generate an access token and refresh token
          access_token = f"access_{secrets.token_hex(32)}"
          refresh_token = f"refresh_{secrets.token_hex(32)}"
  
          # Store the tokens
          assert client.client_id is not None
          self.tokens[access_token] = AccessToken(
              token=access_token,
              client_id=client.client_id,
              scopes=authorization_code.scopes,
              expires_at=int(time.time()) + 3600,
          )
  
          self.refresh_tokens[refresh_token] = access_token
  
          # Remove the used code
          del self.auth_codes[authorization_code.code]
  
          return OAuthToken(
              access_token=access_token,
              token_type="Bearer",
              expires_in=3600,
              scope="read write",
              refresh_token=refresh_token,
          )
  
      async def load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None:
          old_access_token = self.refresh_tokens.get(refresh_token)
          if old_access_token is None:
              return None
          token_info = self.tokens.get(old_access_token)
          if token_info is None:  # pragma: no cover
              return None
  
          # Create a RefreshToken object that matches what is expected in later code
          refresh_obj = RefreshToken(
              token=refresh_token,
              client_id=token_info.client_id,
              scopes=token_info.scopes,
              expires_at=token_info.expires_at,
          )
  
          return refresh_obj
  
      async def exchange_refresh_token(
          self,
          client: OAuthClientInformationFull,
          refresh_token: RefreshToken,
          scopes: list[str],
      ) -> OAuthToken:
          # Check if refresh token exists
          assert refresh_token.token in self.refresh_tokens
  
          old_access_token = self.refresh_tokens[refresh_token.token]
  
          # Check if the access token exists
          assert old_access_token in self.tokens
  
          # Check if the token was issued to this client
          token_info = self.tokens[old_access_token]
          assert token_info.client_id == client.client_id
  
          # Generate a new access token and refresh token
          new_access_token = f"access_{secrets.token_hex(32)}"
          new_refresh_token = f"refresh_{secrets.token_hex(32)}"
  
          # Store the new tokens
          assert client.client_id is not None
          self.tokens[new_access_token] = AccessToken(
              token=new_access_token,
              client_id=client.client_id,
              scopes=scopes or token_info.scopes,
              expires_at=int(time.time()) + 3600,
          )
  
          self.refresh_tokens[new_refresh_token] = new_access_token
  
          # Remove the old tokens
          del self.refresh_tokens[refresh_token.token]
          del self.tokens[old_access_token]
  
          return OAuthToken(
              access_token=new_access_token,
              token_type="Bearer",
              expires_in=3600,
              scope=" ".join(scopes) if scopes else " ".join(token_info.scopes),
              refresh_token=new_refresh_token,
          )
  
      async def load_access_token(self, token: str) -> AccessToken | None:
          token_info = self.tokens.get(token)
  
          # Check if token is expired
          # if token_info.expires_at < int(time.time()):
          #     raise InvalidTokenError("Access token has expired")
  
          return token_info and AccessToken(
              token=token,
              client_id=token_info.client_id,
              scopes=token_info.scopes,
              expires_at=token_info.expires_at,
          )
  
      async def revoke_token(self, token: AccessToken | RefreshToken) -> None:
          match token:
              case RefreshToken():  # pragma: no cover
                  # Remove the refresh token
                  del self.refresh_tokens[token.token]
  
              case AccessToken():  # pragma: no branch
                  # Remove the access token
                  del self.tokens[token.token]
  
                  # Also remove any refresh tokens that point to this access token
                  for refresh_token, access_token in list(self.refresh_tokens.items()):
                      if access_token == token.token:  # pragma: no branch
                          del self.refresh_tokens[refresh_token]
  
  
  @pytest.fixture
  def mock_oauth_provider():
      return MockOAuthProvider()
  
  
  @pytest.fixture
  def auth_app(mock_oauth_provider: MockOAuthProvider):
      # Create auth router
      auth_routes = create_auth_routes(
          mock_oauth_provider,
          AnyHttpUrl("https://auth.example.com"),
          AnyHttpUrl("https://docs.example.com"),
          client_registration_options=ClientRegistrationOptions(
              enabled=True,
              valid_scopes=["read", "write", "profile"],
              default_scopes=["read", "write"],
          ),
          revocation_options=RevocationOptions(enabled=True),
      )
  
      # Create Starlette app
      app = Starlette(routes=auth_routes)
  
      return app
  
  
  @pytest.fixture
  async def test_client(auth_app: Starlette):
      async with httpx.AsyncClient(transport=httpx.ASGITransport(app=auth_app), base_url="https://mcptest.com") as client:
          yield client
  
  
  @pytest.fixture
  async def registered_client(
      test_client: httpx.AsyncClient, request: pytest.FixtureRequest
  ) -> OAuthClientInformationFull:
      """Create and register a test client.
  
      Parameters can be customized via indirect parameterization:
      @pytest.mark.parametrize("registered_client",
                              [{"grant_types": ["authorization_code"]}],
                              indirect=True)
      """
      # Default client metadata
      client_metadata = {
          "redirect_uris": ["https://client.example.com/callback"],
          "client_name": "Test Client",
          "grant_types": ["authorization_code", "refresh_token"],
      }
  
      # Override with any parameters from the test
      if hasattr(request, "param") and request.param:
          client_metadata.update(request.param)
  
      response = await test_client.post("/register", json=client_metadata)
      assert response.status_code == 201, f"Failed to register client: {response.content}"
  
      client_info = response.json()
      return client_info
  
  
  @pytest.fixture
  def pkce_challenge():
      """Create a PKCE challenge with code_verifier and code_challenge."""
      code_verifier = "some_random_verifier_string"
      code_challenge = base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest()).decode().rstrip("=")
  
      return {"code_verifier": code_verifier, "code_challenge": code_challenge}
  
  
  @pytest.fixture
  async def auth_code(
      test_client: httpx.AsyncClient,
      registered_client: dict[str, Any],
      pkce_challenge: dict[str, str],
      request: pytest.FixtureRequest,
  ):
      """Get an authorization code.
  
      Parameters can be customized via indirect parameterization:
      @pytest.mark.parametrize("auth_code",
                              [{"redirect_uri": "https://client.example.com/other-callback"}],
                              indirect=True)
      """
      # Default authorize params
      auth_params = {
          "response_type": "code",
          "client_id": registered_client["client_id"],
          "redirect_uri": "https://client.example.com/callback",
          "code_challenge": pkce_challenge["code_challenge"],
          "code_challenge_method": "S256",
          "state": "test_state",
      }
  
      # Override with any parameters from the test
      if hasattr(request, "param") and request.param:  # pragma: no cover
          auth_params.update(request.param)
  
      response = await test_client.get("/authorize", params=auth_params)
      assert response.status_code == 302, f"Failed to get auth code: {response.content}"
  
      # Extract the authorization code
      redirect_url = response.headers["location"]
      parsed_url = urlparse(redirect_url)
      query_params = parse_qs(parsed_url.query)
  
      assert "code" in query_params, f"No code in response: {query_params}"
      auth_code = query_params["code"][0]
  
      return {
          "code": auth_code,
          "redirect_uri": auth_params["redirect_uri"],
          "state": query_params.get("state", [None])[0],
      }
  
  
  class TestAuthEndpoints:
      @pytest.mark.anyio
      async def test_metadata_endpoint(self, test_client: httpx.AsyncClient):
          """Test the OAuth 2.0 metadata endpoint."""
  
          response = await test_client.get("/.well-known/oauth-authorization-server")
          assert response.status_code == 200
  
          metadata = response.json()
          assert metadata["issuer"] == "https://auth.example.com/"
          assert metadata["authorization_endpoint"] == "https://auth.example.com/authorize"
          assert metadata["token_endpoint"] == "https://auth.example.com/token"
          assert metadata["registration_endpoint"] == "https://auth.example.com/register"
          assert metadata["revocation_endpoint"] == "https://auth.example.com/revoke"
          assert metadata["response_types_supported"] == ["code"]
          assert metadata["code_challenge_methods_supported"] == ["S256"]
          assert metadata["token_endpoint_auth_methods_supported"] == ["client_secret_post", "client_secret_basic"]
          assert metadata["grant_types_supported"] == [
              "authorization_code",
              "refresh_token",
          ]
          assert metadata["service_documentation"] == "https://docs.example.com/"
  
      @pytest.mark.anyio
      async def test_token_validation_error(self, test_client: httpx.AsyncClient):
          """Test token endpoint error - validation error."""
          # Missing required fields
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  # Missing code, code_verifier, client_id, etc.
              },
          )
          error_response = response.json()
          # Per RFC 6749 Section 5.2, authentication failures (missing client_id)
          # must return "invalid_client", not "unauthorized_client"
          assert error_response["error"] == "invalid_client"
          assert "error_description" in error_response  # Contains error message
  
      @pytest.mark.anyio
      async def test_token_invalid_client_secret_returns_invalid_client(
          self,
          test_client: httpx.AsyncClient,
          registered_client: dict[str, Any],
          pkce_challenge: dict[str, str],
          mock_oauth_provider: MockOAuthProvider,
      ):
          """Test token endpoint returns 'invalid_client' for wrong client_secret per RFC 6749.
  
          RFC 6749 Section 5.2 defines:
          - invalid_client: Client authentication failed (wrong credentials, unknown client)
          - unauthorized_client: Authenticated client not authorized for grant type
  
          When client_secret is wrong, this is an authentication failure, so the
          error code MUST be 'invalid_client'.
          """
          # Create an auth code for the registered client
          auth_code = f"code_{int(time.time())}"
          mock_oauth_provider.auth_codes[auth_code] = AuthorizationCode(
              code=auth_code,
              client_id=registered_client["client_id"],
              code_challenge=pkce_challenge["code_challenge"],
              redirect_uri=AnyUrl("https://client.example.com/callback"),
              redirect_uri_provided_explicitly=True,
              scopes=["read", "write"],
              expires_at=time.time() + 600,
          )
  
          # Try to exchange the auth code with a WRONG client_secret
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  "client_id": registered_client["client_id"],
                  "client_secret": "wrong_secret_that_does_not_match",
                  "code": auth_code,
                  "code_verifier": pkce_challenge["code_verifier"],
                  "redirect_uri": "https://client.example.com/callback",
              },
          )
  
          assert response.status_code == 401
          error_response = response.json()
          # RFC 6749 Section 5.2: authentication failures MUST return "invalid_client"
          assert error_response["error"] == "invalid_client"
          assert "Invalid client_secret" in error_response["error_description"]
  
      @pytest.mark.anyio
      async def test_token_invalid_auth_code(
          self,
          test_client: httpx.AsyncClient,
          registered_client: dict[str, Any],
          pkce_challenge: dict[str, str],
      ):
          """Test token endpoint error - authorization code does not exist."""
          # Try to use a non-existent authorization code
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  "client_id": registered_client["client_id"],
                  "client_secret": registered_client["client_secret"],
                  "code": "non_existent_auth_code",
                  "code_verifier": pkce_challenge["code_verifier"],
                  "redirect_uri": "https://client.example.com/callback",
              },
          )
  
          assert response.status_code == 400
          error_response = response.json()
          assert error_response["error"] == "invalid_grant"
          assert "authorization code does not exist" in error_response["error_description"]
  
      @pytest.mark.anyio
      async def test_token_expired_auth_code(
          self,
          test_client: httpx.AsyncClient,
          registered_client: dict[str, Any],
          auth_code: dict[str, str],
          pkce_challenge: dict[str, str],
          mock_oauth_provider: MockOAuthProvider,
      ):
          """Test token endpoint error - authorization code has expired."""
          # Get the current time for our time mocking
          current_time = time.time()
  
          # Find the auth code object
          code_value = auth_code["code"]
          found_code = None
          for code_obj in mock_oauth_provider.auth_codes.values():  # pragma: no branch
              if code_obj.code == code_value:  # pragma: no branch
                  found_code = code_obj
                  break
  
          assert found_code is not None
  
          # Authorization codes are typically short-lived (5 minutes = 300 seconds)
          # So we'll mock time to be 10 minutes (600 seconds) in the future
          with unittest.mock.patch("time.time", return_value=current_time + 600):
              # Try to use the expired authorization code
              response = await test_client.post(
                  "/token",
                  data={
                      "grant_type": "authorization_code",
                      "client_id": registered_client["client_id"],
                      "client_secret": registered_client["client_secret"],
                      "code": code_value,
                      "code_verifier": pkce_challenge["code_verifier"],
                      "redirect_uri": auth_code["redirect_uri"],
                  },
              )
              assert response.status_code == 400
              error_response = response.json()
              assert error_response["error"] == "invalid_grant"
              assert "authorization code has expired" in error_response["error_description"]
  
      @pytest.mark.anyio
      @pytest.mark.parametrize(
          "registered_client",
          [
              {
                  "redirect_uris": [
                      "https://client.example.com/callback",
                      "https://client.example.com/other-callback",
                  ]
              }
          ],
          indirect=True,
      )
      async def test_token_redirect_uri_mismatch(
          self,
          test_client: httpx.AsyncClient,
          registered_client: dict[str, Any],
          auth_code: dict[str, str],
          pkce_challenge: dict[str, str],
      ):
          """Test token endpoint error - redirect URI mismatch."""
          # Try to use the code with a different redirect URI
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  "client_id": registered_client["client_id"],
                  "client_secret": registered_client["client_secret"],
                  "code": auth_code["code"],
                  "code_verifier": pkce_challenge["code_verifier"],
                  # Different from the one used in /authorize
                  "redirect_uri": "https://client.example.com/other-callback",
              },
          )
          assert response.status_code == 400
          error_response = response.json()
          assert error_response["error"] == "invalid_request"
          assert "redirect_uri did not match" in error_response["error_description"]
  
      @pytest.mark.anyio
      async def test_token_code_verifier_mismatch(
          self, test_client: httpx.AsyncClient, registered_client: dict[str, Any], auth_code: dict[str, str]
      ):
          """Test token endpoint error - PKCE code verifier mismatch."""
          # Try to use the code with an incorrect code verifier
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  "client_id": registered_client["client_id"],
                  "client_secret": registered_client["client_secret"],
                  "code": auth_code["code"],
                  # Different from the one used to create challenge
                  "code_verifier": "incorrect_code_verifier",
                  "redirect_uri": auth_code["redirect_uri"],
              },
          )
          assert response.status_code == 400
          error_response = response.json()
          assert error_response["error"] == "invalid_grant"
          assert "incorrect code_verifier" in error_response["error_description"]
  
      @pytest.mark.anyio
      async def test_token_invalid_refresh_token(self, test_client: httpx.AsyncClient, registered_client: dict[str, Any]):
          """Test token endpoint error - refresh token does not exist."""
          # Try to use a non-existent refresh token
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "refresh_token",
                  "client_id": registered_client["client_id"],
                  "client_secret": registered_client["client_secret"],
                  "refresh_token": "non_existent_refresh_token",
              },
          )
          assert response.status_code == 400
          error_response = response.json()
          assert error_response["error"] == "invalid_grant"
          assert "refresh token does not exist" in error_response["error_description"]
  
      @pytest.mark.anyio
      async def test_token_expired_refresh_token(
          self,
          test_client: httpx.AsyncClient,
          registered_client: dict[str, Any],
          auth_code: dict[str, str],
          pkce_challenge: dict[str, str],
      ):
          """Test token endpoint error - refresh token has expired."""
          # Step 1: First, let's create a token and refresh token at the current time
          current_time = time.time()
  
          # Exchange authorization code for tokens normally
          token_response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  "client_id": registered_client["client_id"],
                  "client_secret": registered_client["client_secret"],
                  "code": auth_code["code"],
                  "code_verifier": pkce_challenge["code_verifier"],
                  "redirect_uri": auth_code["redirect_uri"],
              },
          )
          assert token_response.status_code == 200
          tokens = token_response.json()
          refresh_token = tokens["refresh_token"]
  
          # Step 2: Time travel forward 4 hours (tokens expire in 1 hour by default)
          # Mock the time.time() function to return a value 4 hours in the future
          with unittest.mock.patch("time.time", return_value=current_time + 14400):  # 4 hours = 14400 seconds
              # Try to use the refresh token which should now be considered expired
              response = await test_client.post(
                  "/token",
                  data={
                      "grant_type": "refresh_token",
                      "client_id": registered_client["client_id"],
                      "client_secret": registered_client["client_secret"],
                      "refresh_token": refresh_token,
                  },
              )
  
              # In the "future", the token should be considered expired
              assert response.status_code == 400
              error_response = response.json()
              assert error_response["error"] == "invalid_grant"
              assert "refresh token has expired" in error_response["error_description"]
  
      @pytest.mark.anyio
      async def test_token_invalid_scope(
          self,
          test_client: httpx.AsyncClient,
          registered_client: dict[str, Any],
          auth_code: dict[str, str],
          pkce_challenge: dict[str, str],
      ):
          """Test token endpoint error - invalid scope in refresh token request."""
          # Exchange authorization code for tokens
          token_response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  "client_id": registered_client["client_id"],
                  "client_secret": registered_client["client_secret"],
                  "code": auth_code["code"],
                  "code_verifier": pkce_challenge["code_verifier"],
                  "redirect_uri": auth_code["redirect_uri"],
              },
          )
          assert token_response.status_code == 200
  
          tokens = token_response.json()
          refresh_token = tokens["refresh_token"]
  
          # Try to use refresh token with an invalid scope
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "refresh_token",
                  "client_id": registered_client["client_id"],
                  "client_secret": registered_client["client_secret"],
                  "refresh_token": refresh_token,
                  "scope": "read write invalid_scope",  # Adding an invalid scope
              },
          )
          assert response.status_code == 400
          error_response = response.json()
          assert error_response["error"] == "invalid_scope"
          assert "cannot request scope" in error_response["error_description"]
  
      @pytest.mark.anyio
      async def test_client_registration(self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider):
          """Test client registration."""
          client_metadata = {
              "redirect_uris": ["https://client.example.com/callback"],
              "client_name": "Test Client",
              "client_uri": "https://client.example.com",
          }
  
          response = await test_client.post(
              "/register",
              json=client_metadata,
          )
          assert response.status_code == 201, response.content
  
          client_info = response.json()
          assert "client_id" in client_info
          assert "client_secret" in client_info
          assert client_info["client_name"] == "Test Client"
          assert client_info["redirect_uris"] == ["https://client.example.com/callback"]
  
          # Verify that the client was registered
          # assert await mock_oauth_provider.clients_store.get_client(
          #     client_info["client_id"]
          # ) is not None
  
      @pytest.mark.anyio
      async def test_client_registration_missing_required_fields(self, test_client: httpx.AsyncClient):
          """Test client registration with missing required fields."""
          # Missing redirect_uris which is a required field
          client_metadata = {
              "client_name": "Test Client",
              "client_uri": "https://client.example.com",
          }
  
          response = await test_client.post(
              "/register",
              json=client_metadata,
          )
          assert response.status_code == 400
          error_data = response.json()
          assert "error" in error_data
          assert error_data["error"] == "invalid_client_metadata"
          assert error_data["error_description"] == "redirect_uris: Field required"
  
      @pytest.mark.anyio
      async def test_client_registration_invalid_uri(self, test_client: httpx.AsyncClient):
          """Test client registration with invalid URIs."""
          # Invalid redirect_uri format
          client_metadata = {
              "redirect_uris": ["not-a-valid-uri"],
              "client_name": "Test Client",
          }
  
          response = await test_client.post(
              "/register",
              json=client_metadata,
          )
          assert response.status_code == 400
          error_data = response.json()
          assert "error" in error_data
          assert error_data["error"] == "invalid_client_metadata"
          assert error_data["error_description"] == (
              "redirect_uris.0: Input should be a valid URL, relative URL without a base"
          )
  
      @pytest.mark.anyio
      async def test_client_registration_empty_redirect_uris(self, test_client: httpx.AsyncClient):
          """Test client registration with empty redirect_uris array."""
          redirect_uris: list[str] = []
          client_metadata = {
              "redirect_uris": redirect_uris,  # Empty array
              "client_name": "Test Client",
          }
  
          response = await test_client.post(
              "/register",
              json=client_metadata,
          )
          assert response.status_code == 400
          error_data = response.json()
          assert "error" in error_data
          assert error_data["error"] == "invalid_client_metadata"
          assert (
              error_data["error_description"] == "redirect_uris: List should have at least 1 item after validation, not 0"
          )
  
      @pytest.mark.anyio
      async def test_authorize_form_post(self, test_client: httpx.AsyncClient, pkce_challenge: dict[str, str]):
          """Test the authorization endpoint using POST with form-encoded data."""
          # Register a client
          client_metadata = {
              "redirect_uris": ["https://client.example.com/callback"],
              "client_name": "Test Client",
              "grant_types": ["authorization_code", "refresh_token"],
          }
  
          response = await test_client.post(
              "/register",
              json=client_metadata,
          )
          assert response.status_code == 201
          client_info = response.json()
  
          # Use POST with form-encoded data for authorization
          response = await test_client.post(
              "/authorize",
              data={
                  "response_type": "code",
                  "client_id": client_info["client_id"],
                  "redirect_uri": "https://client.example.com/callback",
                  "code_challenge": pkce_challenge["code_challenge"],
                  "code_challenge_method": "S256",
                  "state": "test_form_state",
              },
          )
          assert response.status_code == 302
  
          # Extract the authorization code from the redirect URL
          redirect_url = response.headers["location"]
          parsed_url = urlparse(redirect_url)
          query_params = parse_qs(parsed_url.query)
  
          assert "code" in query_params
          assert query_params["state"][0] == "test_form_state"
  
      @pytest.mark.anyio
      async def test_authorization_get(
          self,
          test_client: httpx.AsyncClient,
          mock_oauth_provider: MockOAuthProvider,
          pkce_challenge: dict[str, str],
      ):
          """Test the full authorization flow."""
          # 1. Register a client
          client_metadata = {
              "redirect_uris": ["https://client.example.com/callback"],
              "client_name": "Test Client",
              "grant_types": ["authorization_code", "refresh_token"],
          }
  
          response = await test_client.post(
              "/register",
              json=client_metadata,
          )
          assert response.status_code == 201
          client_info = response.json()
  
          # 2. Request authorization using GET with query params
          response = await test_client.get(
              "/authorize",
              params={
                  "response_type": "code",
                  "client_id": client_info["client_id"],
                  "redirect_uri": "https://client.example.com/callback",
                  "code_challenge": pkce_challenge["code_challenge"],
                  "code_challenge_method": "S256",
                  "state": "test_state",
              },
          )
          assert response.status_code == 302
  
          # 3. Extract the authorization code from the redirect URL
          redirect_url = response.headers["location"]
          parsed_url = urlparse(redirect_url)
          query_params = parse_qs(parsed_url.query)
  
          assert "code" in query_params
          assert query_params["state"][0] == "test_state"
          auth_code = query_params["code"][0]
  
          # 4. Exchange the authorization code for tokens
          response = await test_client.post(
              "/token",
              data={
                  "grant_type": "authorization_code",
                  "client_id": client_info["client_id"],
                  "client_secret": client_info["client_secret"],
                  "code": auth_code,
                  "code_verifier": pkce_challenge["code_verifier"],
                  "redirect_uri": "https://client.example.com/callback",
              },
          )
          assert response.status_code == 200
  
          token_response = response.json()
          assert "access_token" in token_response
          assert "token_type" in token_response
          assert "refresh_token" in token_response
          assert "expires_in" in token_response
          assert token_response["token_type"] == "Bearer"
  
          # 5. Verify the access token
          access_token = token_response["access_token"]
          refresh_token = token_response["refresh_token"]
  
          # Create a test client with the token
          auth_info = await mock_oauth_provider.load_access_token(access_token)
          assert auth_info
          assert auth_info.client_id == client_info["client_id"]
          assert "read" in auth_info.scopes
          assert "write" in auth_info.scopes
  
          # 6. Refresh the token
  