chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,174 @@
|
||||
# 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.
|
||||
|
||||
"""Tests for CredentialRefresherRegistry."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
from google.adk.auth.auth_credential import AuthCredentialTypes
|
||||
from google.adk.auth.refresher.base_credential_refresher import BaseCredentialRefresher
|
||||
from google.adk.auth.refresher.credential_refresher_registry import CredentialRefresherRegistry
|
||||
|
||||
|
||||
class TestCredentialRefresherRegistry:
|
||||
"""Tests for the CredentialRefresherRegistry class."""
|
||||
|
||||
def test_init(self):
|
||||
"""Test that registry initializes with empty refreshers dictionary."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
assert registry._refreshers == {}
|
||||
|
||||
def test_register_refresher(self):
|
||||
"""Test registering a refresher instance for a credential type."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
mock_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
|
||||
registry.register(AuthCredentialTypes.OAUTH2, mock_refresher)
|
||||
|
||||
assert registry._refreshers[AuthCredentialTypes.OAUTH2] == mock_refresher
|
||||
|
||||
def test_register_multiple_refreshers(self):
|
||||
"""Test registering multiple refresher instances for different credential types."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
mock_oauth2_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
mock_openid_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
mock_service_account_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
|
||||
registry.register(AuthCredentialTypes.OAUTH2, mock_oauth2_refresher)
|
||||
registry.register(
|
||||
AuthCredentialTypes.OPEN_ID_CONNECT, mock_openid_refresher
|
||||
)
|
||||
registry.register(
|
||||
AuthCredentialTypes.SERVICE_ACCOUNT, mock_service_account_refresher
|
||||
)
|
||||
|
||||
assert (
|
||||
registry._refreshers[AuthCredentialTypes.OAUTH2]
|
||||
== mock_oauth2_refresher
|
||||
)
|
||||
assert (
|
||||
registry._refreshers[AuthCredentialTypes.OPEN_ID_CONNECT]
|
||||
== mock_openid_refresher
|
||||
)
|
||||
assert (
|
||||
registry._refreshers[AuthCredentialTypes.SERVICE_ACCOUNT]
|
||||
== mock_service_account_refresher
|
||||
)
|
||||
|
||||
def test_register_overwrite_existing_refresher(self):
|
||||
"""Test that registering a refresher overwrites an existing one for the same credential type."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
mock_refresher_1 = Mock(spec=BaseCredentialRefresher)
|
||||
mock_refresher_2 = Mock(spec=BaseCredentialRefresher)
|
||||
|
||||
# Register first refresher
|
||||
registry.register(AuthCredentialTypes.OAUTH2, mock_refresher_1)
|
||||
assert registry._refreshers[AuthCredentialTypes.OAUTH2] == mock_refresher_1
|
||||
|
||||
# Register second refresher for same credential type
|
||||
registry.register(AuthCredentialTypes.OAUTH2, mock_refresher_2)
|
||||
assert registry._refreshers[AuthCredentialTypes.OAUTH2] == mock_refresher_2
|
||||
|
||||
def test_get_refresher_existing(self):
|
||||
"""Test getting a refresher instance for a registered credential type."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
mock_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
|
||||
registry.register(AuthCredentialTypes.OAUTH2, mock_refresher)
|
||||
result = registry.get_refresher(AuthCredentialTypes.OAUTH2)
|
||||
|
||||
assert result == mock_refresher
|
||||
|
||||
def test_get_refresher_non_existing(self):
|
||||
"""Test getting a refresher instance for a non-registered credential type returns None."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
|
||||
result = registry.get_refresher(AuthCredentialTypes.OAUTH2)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_get_refresher_after_registration(self):
|
||||
"""Test getting refresher instances for multiple credential types."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
mock_oauth2_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
mock_api_key_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
|
||||
registry.register(AuthCredentialTypes.OAUTH2, mock_oauth2_refresher)
|
||||
registry.register(AuthCredentialTypes.API_KEY, mock_api_key_refresher)
|
||||
|
||||
# Get registered refreshers
|
||||
oauth2_result = registry.get_refresher(AuthCredentialTypes.OAUTH2)
|
||||
api_key_result = registry.get_refresher(AuthCredentialTypes.API_KEY)
|
||||
|
||||
assert oauth2_result == mock_oauth2_refresher
|
||||
assert api_key_result == mock_api_key_refresher
|
||||
|
||||
# Get non-registered refresher
|
||||
http_result = registry.get_refresher(AuthCredentialTypes.HTTP)
|
||||
assert http_result is None
|
||||
|
||||
def test_register_all_credential_types(self):
|
||||
"""Test registering refreshers for all available credential types."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
|
||||
refreshers = {}
|
||||
for credential_type in AuthCredentialTypes:
|
||||
mock_refresher = Mock(spec=BaseCredentialRefresher)
|
||||
refreshers[credential_type] = mock_refresher
|
||||
registry.register(credential_type, mock_refresher)
|
||||
|
||||
# Verify all refreshers are registered correctly
|
||||
for credential_type in AuthCredentialTypes:
|
||||
result = registry.get_refresher(credential_type)
|
||||
assert result == refreshers[credential_type]
|
||||
|
||||
def test_empty_registry_get_refresher(self):
|
||||
"""Test getting refresher from empty registry returns None for any credential type."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
|
||||
for credential_type in AuthCredentialTypes:
|
||||
result = registry.get_refresher(credential_type)
|
||||
assert result is None
|
||||
|
||||
def test_registry_independence(self):
|
||||
"""Test that multiple registry instances are independent."""
|
||||
registry1 = CredentialRefresherRegistry()
|
||||
registry2 = CredentialRefresherRegistry()
|
||||
|
||||
mock_refresher1 = Mock(spec=BaseCredentialRefresher)
|
||||
mock_refresher2 = Mock(spec=BaseCredentialRefresher)
|
||||
|
||||
registry1.register(AuthCredentialTypes.OAUTH2, mock_refresher1)
|
||||
registry2.register(AuthCredentialTypes.OAUTH2, mock_refresher2)
|
||||
|
||||
# Verify registries are independent
|
||||
assert (
|
||||
registry1.get_refresher(AuthCredentialTypes.OAUTH2) == mock_refresher1
|
||||
)
|
||||
assert (
|
||||
registry2.get_refresher(AuthCredentialTypes.OAUTH2) == mock_refresher2
|
||||
)
|
||||
assert registry1.get_refresher(
|
||||
AuthCredentialTypes.OAUTH2
|
||||
) != registry2.get_refresher(AuthCredentialTypes.OAUTH2)
|
||||
|
||||
def test_register_with_none_refresher(self):
|
||||
"""Test registering None as a refresher instance."""
|
||||
registry = CredentialRefresherRegistry()
|
||||
|
||||
# This should technically work as the registry accepts any value
|
||||
registry.register(AuthCredentialTypes.OAUTH2, None)
|
||||
result = registry.get_refresher(AuthCredentialTypes.OAUTH2)
|
||||
|
||||
assert result is None
|
||||
@@ -0,0 +1,179 @@
|
||||
# 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 time
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import patch
|
||||
|
||||
from authlib.oauth2.rfc6749 import OAuth2Token
|
||||
from google.adk.auth.auth_credential import AuthCredential
|
||||
from google.adk.auth.auth_credential import AuthCredentialTypes
|
||||
from google.adk.auth.auth_credential import OAuth2Auth
|
||||
from google.adk.auth.auth_schemes import OpenIdConnectWithConfig
|
||||
from google.adk.auth.refresher.oauth2_credential_refresher import OAuth2CredentialRefresher
|
||||
import pytest
|
||||
|
||||
|
||||
class TestOAuth2CredentialRefresher:
|
||||
"""Test suite for OAuth2CredentialRefresher."""
|
||||
|
||||
@patch("google.adk.auth.refresher.oauth2_credential_refresher.OAuth2Token")
|
||||
@pytest.mark.asyncio
|
||||
async def test_needs_refresh_token_not_expired(self, mock_oauth2_token):
|
||||
"""Test needs_refresh when token is not expired."""
|
||||
mock_token_instance = Mock()
|
||||
mock_token_instance.is_expired.return_value = False
|
||||
mock_oauth2_token.return_value = mock_token_instance
|
||||
|
||||
scheme = OpenIdConnectWithConfig(
|
||||
type_="openIdConnect",
|
||||
openId_connect_url=(
|
||||
"https://example.com/.well-known/openid_configuration"
|
||||
),
|
||||
authorization_endpoint="https://example.com/auth",
|
||||
token_endpoint="https://example.com/token",
|
||||
scopes=["openid"],
|
||||
)
|
||||
credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
|
||||
oauth2=OAuth2Auth(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
access_token="existing_token",
|
||||
expires_at=int(time.time()) + 3600,
|
||||
),
|
||||
)
|
||||
|
||||
refresher = OAuth2CredentialRefresher()
|
||||
needs_refresh = await refresher.is_refresh_needed(credential, scheme)
|
||||
|
||||
assert not needs_refresh
|
||||
|
||||
@patch("google.adk.auth.refresher.oauth2_credential_refresher.OAuth2Token")
|
||||
@pytest.mark.asyncio
|
||||
async def test_needs_refresh_token_expired(self, mock_oauth2_token):
|
||||
"""Test needs_refresh when token is expired."""
|
||||
mock_token_instance = Mock()
|
||||
mock_token_instance.is_expired.return_value = True
|
||||
mock_oauth2_token.return_value = mock_token_instance
|
||||
|
||||
scheme = OpenIdConnectWithConfig(
|
||||
type_="openIdConnect",
|
||||
openId_connect_url=(
|
||||
"https://example.com/.well-known/openid_configuration"
|
||||
),
|
||||
authorization_endpoint="https://example.com/auth",
|
||||
token_endpoint="https://example.com/token",
|
||||
scopes=["openid"],
|
||||
)
|
||||
credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
|
||||
oauth2=OAuth2Auth(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
access_token="existing_token",
|
||||
expires_at=int(time.time()) - 3600, # Expired
|
||||
),
|
||||
)
|
||||
|
||||
refresher = OAuth2CredentialRefresher()
|
||||
needs_refresh = await refresher.is_refresh_needed(credential, scheme)
|
||||
|
||||
assert needs_refresh
|
||||
|
||||
@patch("google.adk.auth.oauth2_credential_util.OAuth2Session")
|
||||
@patch("google.adk.auth.oauth2_credential_util.OAuth2Token")
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_token_expired_success(
|
||||
self, mock_oauth2_token, mock_oauth2_session
|
||||
):
|
||||
"""Test successful token refresh when token is expired."""
|
||||
# Setup mock token
|
||||
mock_token_instance = Mock()
|
||||
mock_token_instance.is_expired.return_value = True
|
||||
mock_oauth2_token.return_value = mock_token_instance
|
||||
|
||||
# Setup mock session
|
||||
mock_client = Mock()
|
||||
mock_oauth2_session.return_value = mock_client
|
||||
mock_tokens = OAuth2Token({
|
||||
"access_token": "refreshed_access_token",
|
||||
"refresh_token": "refreshed_refresh_token",
|
||||
"expires_at": int(time.time()) + 3600,
|
||||
"expires_in": 3600,
|
||||
})
|
||||
mock_client.refresh_token.return_value = mock_tokens
|
||||
|
||||
scheme = OpenIdConnectWithConfig(
|
||||
type_="openIdConnect",
|
||||
openId_connect_url=(
|
||||
"https://example.com/.well-known/openid_configuration"
|
||||
),
|
||||
authorization_endpoint="https://example.com/auth",
|
||||
token_endpoint="https://example.com/token",
|
||||
scopes=["openid"],
|
||||
)
|
||||
credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
|
||||
oauth2=OAuth2Auth(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
access_token="old_token",
|
||||
refresh_token="old_refresh_token",
|
||||
expires_at=int(time.time()) - 3600, # Expired
|
||||
),
|
||||
)
|
||||
|
||||
refresher = OAuth2CredentialRefresher()
|
||||
result = await refresher.refresh(credential, scheme)
|
||||
|
||||
# Verify token refresh was successful
|
||||
assert result.oauth2.access_token == "refreshed_access_token"
|
||||
assert result.oauth2.refresh_token == "refreshed_refresh_token"
|
||||
mock_client.refresh_token.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_no_oauth2_credential(self):
|
||||
"""Test refresh with no OAuth2 credential returns original."""
|
||||
scheme = OpenIdConnectWithConfig(
|
||||
type_="openIdConnect",
|
||||
openId_connect_url=(
|
||||
"https://example.com/.well-known/openid_configuration"
|
||||
),
|
||||
authorization_endpoint="https://example.com/auth",
|
||||
token_endpoint="https://example.com/token",
|
||||
scopes=["openid"],
|
||||
)
|
||||
credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
|
||||
# No oauth2 field
|
||||
)
|
||||
|
||||
refresher = OAuth2CredentialRefresher()
|
||||
result = await refresher.refresh(credential, scheme)
|
||||
|
||||
assert result == credential
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_needs_refresh_no_oauth2_credential(self):
|
||||
"""Test needs_refresh with no OAuth2 credential returns False."""
|
||||
credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.HTTP,
|
||||
# No oauth2 field
|
||||
)
|
||||
|
||||
refresher = OAuth2CredentialRefresher()
|
||||
needs_refresh = await refresher.is_refresh_needed(credential, None)
|
||||
|
||||
assert not needs_refresh
|
||||
Reference in New Issue
Block a user