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

304 lines
12 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.
"""Unit tests for _mtls_utils."""
import os
from unittest.mock import MagicMock
from unittest.mock import patch
from google.adk.utils import _mtls_utils
from google.auth import exceptions as ga_exceptions
from google.auth.transport import mtls
import pytest
_DEFAULT_TEMPLATE = "service.{location}.rep.googleapis.com"
_MTLS_TEMPLATE = "service.{location}.rep.mtls.googleapis.com"
_LOCATION = "us-central1"
class TestMtlsUtils:
"""Tests for _mtls_utils functions."""
@patch.object(mtls, "should_use_client_cert", autospec=True)
def test_use_client_cert_effective_with_mtls_cert_true(
self, mock_should_use_client_cert
):
mock_should_use_client_cert.return_value = True
assert _mtls_utils.use_client_cert_effective() is True
mock_should_use_client_cert.assert_called_once()
@patch.object(mtls, "should_use_client_cert", autospec=True)
def test_use_client_cert_effective_with_mtls_cert_false(
self, mock_should_use_client_cert
):
mock_should_use_client_cert.return_value = False
assert _mtls_utils.use_client_cert_effective() is False
mock_should_use_client_cert.assert_called_once()
@patch.object(mtls, "should_use_client_cert", autospec=True)
@patch.dict("os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"})
def test_use_client_cert_effective_fallback_true(
self, mock_should_use_client_cert
):
mock_should_use_client_cert.side_effect = AttributeError
assert _mtls_utils.use_client_cert_effective() is True
@patch.object(mtls, "should_use_client_cert", autospec=True)
@patch.dict("os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"})
def test_use_client_cert_effective_fallback_false(
self, mock_should_use_client_cert
):
mock_should_use_client_cert.side_effect = AttributeError
assert _mtls_utils.use_client_cert_effective() is False
@patch.object(mtls, "should_use_client_cert", autospec=True)
@patch.dict("os.environ", {}, clear=True)
def test_use_client_cert_effective_fallback_default_false(
self, mock_should_use_client_cert
):
mock_should_use_client_cert.side_effect = AttributeError
assert _mtls_utils.use_client_cert_effective() is False
@patch.object(_mtls_utils, "use_client_cert_effective", autospec=True)
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"})
def test_get_api_endpoint_always(self, mock_use_client_cert):
endpoint = _mtls_utils.get_api_endpoint(
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
)
assert endpoint == _MTLS_TEMPLATE.format(location=_LOCATION)
mock_use_client_cert.assert_not_called()
@patch.object(_mtls_utils, "use_client_cert_effective", autospec=True)
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"})
def test_get_api_endpoint_never(self, mock_use_client_cert):
endpoint = _mtls_utils.get_api_endpoint(
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
)
assert endpoint == _DEFAULT_TEMPLATE.format(location=_LOCATION)
mock_use_client_cert.assert_not_called()
@patch.object(_mtls_utils, "use_client_cert_effective", autospec=True)
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
def test_get_api_endpoint_auto_with_cert(self, mock_use_client_cert):
mock_use_client_cert.return_value = True
endpoint = _mtls_utils.get_api_endpoint(
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
)
assert endpoint == _MTLS_TEMPLATE.format(location=_LOCATION)
mock_use_client_cert.assert_called_once()
@patch.object(_mtls_utils, "use_client_cert_effective", autospec=True)
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
def test_get_api_endpoint_auto_without_cert(self, mock_use_client_cert):
mock_use_client_cert.return_value = False
endpoint = _mtls_utils.get_api_endpoint(
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
)
assert endpoint == _DEFAULT_TEMPLATE.format(location=_LOCATION)
mock_use_client_cert.assert_called_once()
@patch.object(_mtls_utils, "use_client_cert_effective", autospec=True)
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "invalid_value"})
def test_get_api_endpoint_invalid_fallback_to_auto(
self, mock_use_client_cert
):
mock_use_client_cert.return_value = True
endpoint = _mtls_utils.get_api_endpoint(
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
)
assert endpoint == _MTLS_TEMPLATE.format(location=_LOCATION)
mock_use_client_cert.assert_called_once()
@pytest.mark.parametrize(
"url, expected",
[
("https://oauth2.googleapis.com/token", True),
("https://openidconnect.googleapis.com/v1/userinfo", True),
("https://oauth2.mtls.googleapis.com/token", False),
("https://example.com/token", False),
("https://accounts.google.com/o/oauth2/v2/auth", False),
(None, False),
("", False),
],
)
def test_is_non_mtls_googleapis_endpoint(self, url, expected):
assert _mtls_utils.is_non_mtls_googleapis_endpoint(url) is expected
@patch.dict("os.environ", {}, clear=True)
@pytest.mark.parametrize(
"url, expected",
[
(
"https://oauth2.googleapis.com/token",
"https://oauth2.mtls.googleapis.com/token",
),
(
"https://openidconnect.googleapis.com/v1/userinfo",
"https://openidconnect.mtls.googleapis.com/v1/userinfo",
),
# Non-Google providers are never rewritten.
("https://example.com/token", "https://example.com/token"),
# Already-mTLS hosts are left alone.
(
"https://oauth2.mtls.googleapis.com/token",
"https://oauth2.mtls.googleapis.com/token",
),
],
)
def test_effective_googleapis_endpoint_rewrites(self, url, expected):
assert _mtls_utils.effective_googleapis_endpoint(url) == expected
@patch.dict(
"os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}, clear=True
)
def test_effective_googleapis_endpoint_never_opts_out(self):
url = "https://oauth2.googleapis.com/token"
assert _mtls_utils.effective_googleapis_endpoint(url) == url
@patch.dict("os.environ", {}, clear=True)
def test_effective_googleapis_endpoint_preserves_query(self):
url = "https://iam.googleapis.com/v1/token?foo=bar"
assert (
_mtls_utils.effective_googleapis_endpoint(url)
== "https://iam.mtls.googleapis.com/v1/token?foo=bar"
)
@patch("google.auth.transport.mtls.has_default_client_cert_source")
@patch("google.auth.transport._mtls_helper.get_client_cert_and_key")
@patch("google.auth.transport.requests._MutualTlsAdapter")
def test_configure_session_for_mtls_mounts_adapter(
self, mock_adapter, mock_get_cert, mock_has_cert_source
):
mock_has_cert_source.return_value = False
mock_get_cert.return_value = (True, b"cert", b"key")
session = MagicMock()
result = _mtls_utils.configure_session_for_mtls(session)
assert result is True
mock_adapter.assert_called_once_with(b"cert", b"key")
session.mount.assert_called_once_with("https://", mock_adapter.return_value)
@patch("google.auth.transport.mtls.has_default_client_cert_source")
@patch("google.auth.transport._mtls_helper.get_client_cert_and_key")
def test_configure_session_for_mtls_no_cert(
self, mock_get_cert, mock_has_cert_source
):
mock_has_cert_source.return_value = False
mock_get_cert.return_value = (False, None, None)
session = MagicMock()
result = _mtls_utils.configure_session_for_mtls(session)
assert result is False
session.mount.assert_not_called()
@patch("google.auth.transport.mtls.has_default_client_cert_source")
@patch("google.auth.transport._mtls_helper.get_client_cert_and_key")
def test_configure_session_for_mtls_cert_error_falls_back(
self, mock_get_cert, mock_has_cert_source
):
mock_has_cert_source.return_value = False
mock_get_cert.side_effect = ga_exceptions.ClientCertError("boom")
session = MagicMock()
result = _mtls_utils.configure_session_for_mtls(session)
assert result is False
session.mount.assert_not_called()
class TestMtlsClientCerts:
"""Tests for MtlsClientCerts."""
@patch.object(mtls, "has_default_client_cert_source", autospec=True)
def test_get_certs_no_default_source(self, mock_has_cert):
mock_has_cert.return_value = False
certs = _mtls_utils.MtlsClientCerts()
cert_path, key_path, passphrase = certs.get_certs()
assert cert_path is None
assert key_path is None
assert passphrase is None
mock_has_cert.assert_called_once()
@patch.object(mtls, "has_default_client_cert_source", autospec=True)
@patch.object(mtls, "default_client_encrypted_cert_source", autospec=True)
def test_get_certs_with_default_source(
self, mock_encrypted_source, mock_has_cert
):
mock_has_cert.return_value = True
mock_cert_source = MagicMock()
mock_cert_source.return_value = (None, None, b"test_passphrase")
mock_encrypted_source.return_value = mock_cert_source
certs = _mtls_utils.MtlsClientCerts()
cert_path, key_path, passphrase = certs.get_certs()
assert cert_path is not None
assert key_path is not None
assert passphrase == b"test_passphrase"
assert os.path.exists(certs._tempdir.name)
assert cert_path.startswith(certs._tempdir.name)
assert key_path.startswith(certs._tempdir.name)
# Getting certs again should return cached values without calling mtls again
cert_path2, key_path2, passphrase2 = certs.get_certs()
assert cert_path2 == cert_path
assert key_path2 == key_path
assert passphrase2 == passphrase
mock_has_cert.assert_called_once()
mock_encrypted_source.assert_called_once()
@patch.object(mtls, "has_default_client_cert_source", autospec=True)
@patch.object(mtls, "default_client_encrypted_cert_source", autospec=True)
def test_get_certs_extraction_failure(
self, mock_encrypted_source, mock_has_cert
):
mock_has_cert.return_value = True
mock_encrypted_source.side_effect = Exception("extraction failed")
certs = _mtls_utils.MtlsClientCerts()
with pytest.raises(
RuntimeError, match="Failed to extract default client certificates"
):
certs.get_certs()
assert certs._tempdir is None
@patch.object(mtls, "has_default_client_cert_source", autospec=True)
@patch.object(mtls, "default_client_encrypted_cert_source", autospec=True)
def test_close_cleans_up_tempdir(self, mock_encrypted_source, mock_has_cert):
mock_has_cert.return_value = True
mock_cert_source = MagicMock()
mock_cert_source.return_value = (None, None, b"test_passphrase")
mock_encrypted_source.return_value = mock_cert_source
certs = _mtls_utils.MtlsClientCerts()
certs.get_certs()
tempdir_name = certs._tempdir.name
assert os.path.exists(tempdir_name)
certs.close()
assert not os.path.exists(tempdir_name)
assert certs._tempdir is None
assert certs.cert_path is None
assert certs.key_path is None
assert certs.passphrase is None
assert not certs._initialized