Files
wehub-resource-sync ec2b666284
Continuous Integration / Pre-commit Linter (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.10) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.11) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.12) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.10) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.11) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.12) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.14) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Waiting to run
Copybara PR Handler / close-imported-pr (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:25:13 +08:00

286 lines
9.3 KiB
Python

# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from unittest.mock import call
from unittest.mock import Mock
from unittest.mock import patch
from google.adk.auth.oauth2_discovery import AuthorizationServerMetadata
from google.adk.auth.oauth2_discovery import OAuth2DiscoveryManager
from google.adk.auth.oauth2_discovery import ProtectedResourceMetadata
import httpx
import pytest
class TestOAuth2Discovery:
"""Tests for the OAuth2DiscoveryManager class."""
@pytest.fixture
def auth_server_metadata(self):
"""Create AuthorizationServerMetadata object."""
return AuthorizationServerMetadata(
issuer="https://auth.example.com",
authorization_endpoint="https://auth.example.com/authorize",
token_endpoint="https://auth.example.com/token",
scopes_supported=["read", "write"],
)
@pytest.fixture
def resource_metadata(self):
"""Create ProtectedResourceMetadata object."""
return ProtectedResourceMetadata(
resource="https://resource.example.com",
authorization_servers=["https://auth.example.com"],
)
@pytest.fixture
def mock_failed_response(self):
"""Create a mock HTTP response with a failure status."""
response = Mock()
response.raise_for_status.side_effect = httpx.HTTPError("Failed")
return response
@pytest.fixture
def mock_empty_response(self):
"""Create a mock HTTP response with an empty JSON body."""
response = Mock()
response.json = lambda: {}
return response
@pytest.fixture
def mock_invalid_json_response(self):
"""Create a mock HTTP response with an invalid JSON body."""
response = Mock()
response.json.side_effect = json.decoder.JSONDecodeError(
"Invalid JSON", "invalid_json", 0
)
return response
def mock_success_response(self, json_data):
"""Create a mock HTTP successful response with auth server metadata."""
response = Mock()
response.json = json_data.model_dump
return response
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_auth_server_metadata_failed(
self,
mock_get,
mock_failed_response,
):
"""Test discovering auth server metadata with failed response."""
mock_get.side_effect = mock_failed_response
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_auth_server_metadata(
"https://auth.example.com"
)
assert not result
mock_get.assert_has_calls([
call(
"https://auth.example.com/.well-known/oauth-authorization-server",
timeout=5,
),
call(
"https://auth.example.com/.well-known/openid-configuration",
timeout=5,
),
])
@pytest.mark.asyncio
async def test_discover_metadata_invalid_url(self):
"""Test discovering resource/auth metadata with an invalid URL."""
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_auth_server_metadata("bad_url")
assert not result
result = await discovery_manager.discover_resource_metadata("bad_url")
assert not result
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_auth_server_metadata_without_path(
self,
mock_get,
auth_server_metadata,
mock_empty_response,
):
"""Test discovering auth server metadata with an issuer URL without a path."""
mock_get.side_effect = [
mock_empty_response,
self.mock_success_response(auth_server_metadata),
]
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_auth_server_metadata(
"https://auth.example.com/"
)
assert result == auth_server_metadata
mock_get.assert_has_calls([
call(
"https://auth.example.com/.well-known/oauth-authorization-server",
timeout=5,
),
call(
"https://auth.example.com/.well-known/openid-configuration",
timeout=5,
),
])
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_auth_server_metadata_with_path(
self,
mock_get,
auth_server_metadata,
mock_failed_response,
mock_invalid_json_response,
):
"""Test discovering auth server metadata with an issuer URL with a path."""
auth_server_metadata.issuer = "https://auth.example.com/oauth"
mock_get.side_effect = [
mock_failed_response,
mock_invalid_json_response,
self.mock_success_response(auth_server_metadata),
]
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_auth_server_metadata(
"https://auth.example.com/oauth"
)
assert result == auth_server_metadata
mock_get.assert_has_calls([
call(
"https://auth.example.com/.well-known/oauth-authorization-server/oauth",
timeout=5,
),
call(
"https://auth.example.com/.well-known/openid-configuration/oauth",
timeout=5,
),
call(
"https://auth.example.com/oauth/.well-known/openid-configuration",
timeout=5,
),
])
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_auth_server_metadata_discard_mismatched_issuer(
self,
mock_get,
auth_server_metadata,
):
"""Test discover_auth_server_metadata() discards response with mismatched issuer."""
bad_auth_server_metadata = auth_server_metadata.model_copy(
update={"issuer": "https://bad.example.com"}
)
mock_get.side_effect = [
self.mock_success_response(bad_auth_server_metadata),
self.mock_success_response(auth_server_metadata),
]
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_auth_server_metadata(
"https://auth.example.com"
)
assert result == auth_server_metadata
mock_get.assert_has_calls([
call(
"https://auth.example.com/.well-known/oauth-authorization-server",
timeout=5,
),
call(
"https://auth.example.com/.well-known/openid-configuration",
timeout=5,
),
])
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_resource_metadata_failed(
self,
mock_get,
mock_failed_response,
):
"""Test discovering resource metadata fails."""
mock_get.return_value = mock_failed_response
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_resource_metadata(
"https://resource.example.com"
)
assert not result
mock_get.assert_called_once_with(
"https://resource.example.com/.well-known/oauth-protected-resource",
timeout=5,
)
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_resource_metadata_without_path(
self, mock_get, resource_metadata
):
"""Test discovering resource metadata with a resource URL without a path."""
mock_get.return_value = self.mock_success_response(resource_metadata)
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_resource_metadata(
"https://resource.example.com/"
)
assert result == resource_metadata
mock_get.assert_called_once_with(
"https://resource.example.com/.well-known/oauth-protected-resource",
timeout=5,
)
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_resource_metadata_with_path(
self, mock_get, resource_metadata
):
"""Test discovering resource metadata with a resource URL with a path."""
resource_metadata.resource = "https://resource.example.com/tenant1"
mock_get.return_value = self.mock_success_response(resource_metadata)
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_resource_metadata(
"https://resource.example.com/tenant1"
)
assert result == resource_metadata
mock_get.assert_called_once_with(
"https://resource.example.com/.well-known/oauth-protected-resource/tenant1",
timeout=5,
)
@patch("httpx.AsyncClient.get")
@pytest.mark.asyncio
async def test_discover_resource_metadata_discard_mismatched_resource(
self,
mock_get,
resource_metadata,
):
"""Test discover_resource_metadata() discards response with mismatched resource."""
resource_metadata.resource = "https://bad.example.com"
mock_get.return_value = self.mock_success_response(resource_metadata)
discovery_manager = OAuth2DiscoveryManager()
result = await discovery_manager.discover_resource_metadata(
"https://resource.example.com"
)
assert not result
mock_get.assert_called_once_with(
"https://resource.example.com/.well-known/oauth-protected-resource",
timeout=5,
)