aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/google-auth/py3/tests
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-10-06 13:42:43 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-10-06 13:52:30 +0300
commit52aed29f744afda4549ef5d64acd0fa8c2092789 (patch)
treee40c9abd25653990d13b68936aee518454df424e /contrib/python/google-auth/py3/tests
parent813943fcad905eee1235d764be4268dddd07ce64 (diff)
downloadydb-52aed29f744afda4549ef5d64acd0fa8c2092789.tar.gz
Intermediate changes
commit_hash:cc4365f5a0e443b92d87079a9c91e77fea2ddcaf
Diffstat (limited to 'contrib/python/google-auth/py3/tests')
-rw-r--r--contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py7
-rw-r--r--contrib/python/google-auth/py3/tests/oauth2/test_credentials.py28
-rw-r--r--contrib/python/google-auth/py3/tests/oauth2/test_service_account.py17
-rw-r--r--contrib/python/google-auth/py3/tests/test__default.py32
-rw-r--r--contrib/python/google-auth/py3/tests/test__exponential_backoff.py41
-rw-r--r--contrib/python/google-auth/py3/tests/test_credentials.py5
-rw-r--r--contrib/python/google-auth/py3/tests/test_external_account.py65
-rw-r--r--contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py16
-rw-r--r--contrib/python/google-auth/py3/tests/test_impersonated_credentials.py17
-rw-r--r--contrib/python/google-auth/py3/tests/transport/aio/test_aiohttp.py170
-rw-r--r--contrib/python/google-auth/py3/tests/transport/aio/test_sessions.py311
-rw-r--r--contrib/python/google-auth/py3/tests/ya.make31
12 files changed, 709 insertions, 31 deletions
diff --git a/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py b/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py
index bb29f8c6e2b..662210fa412 100644
--- a/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py
+++ b/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py
@@ -72,6 +72,13 @@ class TestCredentials(object):
universe_domain=FAKE_UNIVERSE_DOMAIN,
)
+ def test_get_cred_info(self):
+ assert self.credentials.get_cred_info() == {
+ "credential_source": "metadata server",
+ "credential_type": "VM credentials",
+ "principal": "default",
+ }
+
def test_default_state(self):
assert not self.credentials.valid
# Expiration hasn't been set yet
diff --git a/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py b/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py
index 67b6b9c1ad3..a4cac7a4639 100644
--- a/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py
+++ b/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py
@@ -72,6 +72,34 @@ class TestCredentials(object):
assert credentials.rapt_token == self.RAPT_TOKEN
assert credentials.refresh_handler is None
+ def test_get_cred_info(self):
+ credentials = self.make_credentials()
+ credentials._account = "fake-account"
+ assert not credentials.get_cred_info()
+
+ credentials._cred_file_path = "/path/to/file"
+ assert credentials.get_cred_info() == {
+ "credential_source": "/path/to/file",
+ "credential_type": "user credentials",
+ "principal": "fake-account",
+ }
+
+ def test_get_cred_info_no_account(self):
+ credentials = self.make_credentials()
+ assert not credentials.get_cred_info()
+
+ credentials._cred_file_path = "/path/to/file"
+ assert credentials.get_cred_info() == {
+ "credential_source": "/path/to/file",
+ "credential_type": "user credentials",
+ }
+
+ def test__make_copy_get_cred_info(self):
+ credentials = self.make_credentials()
+ credentials._cred_file_path = "/path/to/file"
+ cred_copy = credentials._make_copy()
+ assert cred_copy._cred_file_path == "/path/to/file"
+
def test_token_usage_metrics(self):
credentials = self.make_credentials()
credentials.token = "token"
diff --git a/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py b/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py
index 0dbe316a0f4..fe02e828e73 100644
--- a/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py
+++ b/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py
@@ -69,6 +69,23 @@ class TestCredentials(object):
universe_domain=universe_domain,
)
+ def test_get_cred_info(self):
+ credentials = self.make_credentials()
+ assert not credentials.get_cred_info()
+
+ credentials._cred_file_path = "/path/to/file"
+ assert credentials.get_cred_info() == {
+ "credential_source": "/path/to/file",
+ "credential_type": "service account credentials",
+ "principal": "service-account@example.com",
+ }
+
+ def test__make_copy_get_cred_info(self):
+ credentials = self.make_credentials()
+ credentials._cred_file_path = "/path/to/file"
+ cred_copy = credentials._make_copy()
+ assert cred_copy._cred_file_path == "/path/to/file"
+
def test_constructor_no_universe_domain(self):
credentials = service_account.Credentials(
SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI, universe_domain=None
diff --git a/contrib/python/google-auth/py3/tests/test__default.py b/contrib/python/google-auth/py3/tests/test__default.py
index aaf892f6d04..3147d505dab 100644
--- a/contrib/python/google-auth/py3/tests/test__default.py
+++ b/contrib/python/google-auth/py3/tests/test__default.py
@@ -884,6 +884,38 @@ def test_default_early_out(unused_get):
@mock.patch(
+ "google.auth._default.load_credentials_from_file",
+ return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
+ autospec=True,
+)
+def test_default_cred_file_path_env_var(unused_load_cred, monkeypatch):
+ monkeypatch.setenv(environment_vars.CREDENTIALS, "/path/to/file")
+ cred, _ = _default.default()
+ assert (
+ cred._cred_file_path
+ == "/path/to/file file via the GOOGLE_APPLICATION_CREDENTIALS environment variable"
+ )
+
+
+@mock.patch("os.path.isfile", return_value=True, autospec=True)
+@mock.patch(
+ "google.auth._cloud_sdk.get_application_default_credentials_path",
+ return_value="/path/to/adc/file",
+ autospec=True,
+)
+@mock.patch(
+ "google.auth._default.load_credentials_from_file",
+ return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
+ autospec=True,
+)
+def test_default_cred_file_path_gcloud(
+ unused_load_cred, unused_get_adc_file, unused_isfile
+):
+ cred, _ = _default.default()
+ assert cred._cred_file_path == "/path/to/adc/file"
+
+
+@mock.patch(
"google.auth._default._get_explicit_environ_credentials",
return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
autospec=True,
diff --git a/contrib/python/google-auth/py3/tests/test__exponential_backoff.py b/contrib/python/google-auth/py3/tests/test__exponential_backoff.py
index 95422502b0d..b7b6877b2c0 100644
--- a/contrib/python/google-auth/py3/tests/test__exponential_backoff.py
+++ b/contrib/python/google-auth/py3/tests/test__exponential_backoff.py
@@ -54,3 +54,44 @@ def test_minimum_total_attempts():
with pytest.raises(exceptions.InvalidValue):
_exponential_backoff.ExponentialBackoff(total_attempts=-1)
_exponential_backoff.ExponentialBackoff(total_attempts=1)
+
+
+@pytest.mark.asyncio
+@mock.patch("asyncio.sleep", return_value=None)
+async def test_exponential_backoff_async(mock_time_async):
+ eb = _exponential_backoff.AsyncExponentialBackoff()
+ curr_wait = eb._current_wait_in_seconds
+ iteration_count = 0
+
+ # Workaround issue in python 3.9 related to code coverage by adding `# pragma: no branch`
+ # See https://github.com/googleapis/gapic-generator-python/pull/1174#issuecomment-1025132372
+ async for attempt in eb: # pragma: no branch
+ if attempt == 1:
+ assert mock_time_async.call_count == 0
+ else:
+ backoff_interval = mock_time_async.call_args[0][0]
+ jitter = curr_wait * eb._randomization_factor
+
+ assert (curr_wait - jitter) <= backoff_interval <= (curr_wait + jitter)
+ assert attempt == iteration_count + 1
+ assert eb.backoff_count == iteration_count + 1
+ assert eb._current_wait_in_seconds == eb._multiplier ** iteration_count
+
+ curr_wait = eb._current_wait_in_seconds
+ iteration_count += 1
+
+ assert eb.total_attempts == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS
+ assert eb.backoff_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS
+ assert iteration_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS
+ assert (
+ mock_time_async.call_count
+ == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS - 1
+ )
+
+
+def test_minimum_total_attempts_async():
+ with pytest.raises(exceptions.InvalidValue):
+ _exponential_backoff.AsyncExponentialBackoff(total_attempts=0)
+ with pytest.raises(exceptions.InvalidValue):
+ _exponential_backoff.AsyncExponentialBackoff(total_attempts=-1)
+ _exponential_backoff.AsyncExponentialBackoff(total_attempts=1)
diff --git a/contrib/python/google-auth/py3/tests/test_credentials.py b/contrib/python/google-auth/py3/tests/test_credentials.py
index 8e6bbc96330..e11bcb4e551 100644
--- a/contrib/python/google-auth/py3/tests/test_credentials.py
+++ b/contrib/python/google-auth/py3/tests/test_credentials.py
@@ -52,6 +52,11 @@ def test_credentials_constructor():
assert not credentials._use_non_blocking_refresh
+def test_credentials_get_cred_info():
+ credentials = CredentialsImpl()
+ assert not credentials.get_cred_info()
+
+
def test_with_non_blocking_refresh():
c = CredentialsImpl()
c.with_non_blocking_refresh()
diff --git a/contrib/python/google-auth/py3/tests/test_external_account.py b/contrib/python/google-auth/py3/tests/test_external_account.py
index 3c372e6291c..bddcb4afa1a 100644
--- a/contrib/python/google-auth/py3/tests/test_external_account.py
+++ b/contrib/python/google-auth/py3/tests/test_external_account.py
@@ -275,6 +275,31 @@ class TestCredentials(object):
assert request_kwargs["headers"] == headers
assert "body" not in request_kwargs
+ def test_get_cred_info(self):
+ credentials = self.make_credentials()
+ assert not credentials.get_cred_info()
+
+ credentials._cred_file_path = "/path/to/file"
+ assert credentials.get_cred_info() == {
+ "credential_source": "/path/to/file",
+ "credential_type": "external account credentials",
+ }
+
+ credentials._service_account_impersonation_url = (
+ self.SERVICE_ACCOUNT_IMPERSONATION_URL
+ )
+ assert credentials.get_cred_info() == {
+ "credential_source": "/path/to/file",
+ "credential_type": "external account credentials",
+ "principal": SERVICE_ACCOUNT_EMAIL,
+ }
+
+ def test__make_copy_get_cred_info(self):
+ credentials = self.make_credentials()
+ credentials._cred_file_path = "/path/to/file"
+ cred_copy = credentials._make_copy()
+ assert cred_copy._cred_file_path == "/path/to/file"
+
def test_default_state(self):
credentials = self.make_credentials(
service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL
@@ -469,25 +494,29 @@ class TestCredentials(object):
with mock.patch.object(
external_account.Credentials, "__init__", return_value=None
) as mock_init:
- credentials.with_quota_project("project-foo")
+ new_cred = credentials.with_quota_project("project-foo")
- # Confirm with_quota_project initialized the credential with the
- # expected parameters and quota project ID.
- mock_init.assert_called_once_with(
- audience=self.AUDIENCE,
- subject_token_type=self.SUBJECT_TOKEN_TYPE,
- token_url=self.TOKEN_URL,
- token_info_url=self.TOKEN_INFO_URL,
- credential_source=self.CREDENTIAL_SOURCE,
- service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL,
- service_account_impersonation_options={"token_lifetime_seconds": 2800},
- client_id=CLIENT_ID,
- client_secret=CLIENT_SECRET,
- quota_project_id="project-foo",
- scopes=self.SCOPES,
- default_scopes=["default1"],
- universe_domain=DEFAULT_UNIVERSE_DOMAIN,
- )
+ # Confirm with_quota_project initialized the credential with the
+ # expected parameters.
+ mock_init.assert_called_once_with(
+ audience=self.AUDIENCE,
+ subject_token_type=self.SUBJECT_TOKEN_TYPE,
+ token_url=self.TOKEN_URL,
+ token_info_url=self.TOKEN_INFO_URL,
+ credential_source=self.CREDENTIAL_SOURCE,
+ service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL,
+ service_account_impersonation_options={"token_lifetime_seconds": 2800},
+ client_id=CLIENT_ID,
+ client_secret=CLIENT_SECRET,
+ quota_project_id=self.QUOTA_PROJECT_ID,
+ scopes=self.SCOPES,
+ default_scopes=["default1"],
+ universe_domain=DEFAULT_UNIVERSE_DOMAIN,
+ )
+
+ # Confirm with_quota_project sets the correct quota project after
+ # initialization.
+ assert new_cred.quota_project_id == "project-foo"
def test_info(self):
credentials = self.make_credentials(universe_domain="dummy_universe.com")
diff --git a/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py b/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py
index 743ee9c848d..93926a1314c 100644
--- a/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py
+++ b/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py
@@ -83,6 +83,22 @@ class TestCredentials(object):
return request
+ def test_get_cred_info(self):
+ credentials = self.make_credentials()
+ assert not credentials.get_cred_info()
+
+ credentials._cred_file_path = "/path/to/file"
+ assert credentials.get_cred_info() == {
+ "credential_source": "/path/to/file",
+ "credential_type": "external account authorized user credentials",
+ }
+
+ def test__make_copy_get_cred_info(self):
+ credentials = self.make_credentials()
+ credentials._cred_file_path = "/path/to/file"
+ cred_copy = credentials._make_copy()
+ assert cred_copy._cred_file_path == "/path/to/file"
+
def test_default_state(self):
creds = self.make_credentials()
diff --git a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py
index 7295bba4292..4fb68103a8d 100644
--- a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py
+++ b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py
@@ -136,6 +136,23 @@ class TestImpersonatedCredentials(object):
iam_endpoint_override=iam_endpoint_override,
)
+ def test_get_cred_info(self):
+ credentials = self.make_credentials()
+ assert not credentials.get_cred_info()
+
+ credentials._cred_file_path = "/path/to/file"
+ assert credentials.get_cred_info() == {
+ "credential_source": "/path/to/file",
+ "credential_type": "impersonated credentials",
+ "principal": "impersonated@project.iam.gserviceaccount.com",
+ }
+
+ def test__make_copy_get_cred_info(self):
+ credentials = self.make_credentials()
+ credentials._cred_file_path = "/path/to/file"
+ cred_copy = credentials._make_copy()
+ assert cred_copy._cred_file_path == "/path/to/file"
+
def test_make_from_user_credentials(self):
credentials = self.make_credentials(
source_credentials=self.USER_SOURCE_CREDENTIALS
diff --git a/contrib/python/google-auth/py3/tests/transport/aio/test_aiohttp.py b/contrib/python/google-auth/py3/tests/transport/aio/test_aiohttp.py
new file mode 100644
index 00000000000..632abff25a4
--- /dev/null
+++ b/contrib/python/google-auth/py3/tests/transport/aio/test_aiohttp.py
@@ -0,0 +1,170 @@
+# Copyright 2024 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 asyncio
+
+from aioresponses import aioresponses # type: ignore
+from mock import AsyncMock, Mock, patch
+import pytest # type: ignore
+import pytest_asyncio # type: ignore
+
+from google.auth import exceptions
+import google.auth.aio.transport.aiohttp as auth_aiohttp
+
+
+try:
+ import aiohttp # type: ignore
+except ImportError as caught_exc: # pragma: NO COVER
+ raise ImportError(
+ "The aiohttp library is not installed from please install the aiohttp package to use the aiohttp transport."
+ ) from caught_exc
+
+
+@pytest.fixture
+def mock_response():
+ response = Mock()
+ response.status = 200
+ response.headers = {"Content-Type": "application/json", "Content-Length": "100"}
+ mock_iterator = AsyncMock()
+ mock_iterator.__aiter__.return_value = iter(
+ [b"Cavefish ", b"have ", b"no ", b"sight."]
+ )
+ response.content.iter_chunked = lambda chunk_size: mock_iterator
+ response.read = AsyncMock(return_value=b"Cavefish have no sight.")
+ response.close = AsyncMock()
+
+ return auth_aiohttp.Response(response)
+
+
+class TestResponse(object):
+ @pytest.mark.asyncio
+ async def test_response_status_code(self, mock_response):
+ assert mock_response.status_code == 200
+
+ @pytest.mark.asyncio
+ async def test_response_headers(self, mock_response):
+ assert mock_response.headers["Content-Type"] == "application/json"
+ assert mock_response.headers["Content-Length"] == "100"
+
+ @pytest.mark.asyncio
+ async def test_response_content(self, mock_response):
+ content = b"".join([chunk async for chunk in mock_response.content()])
+ assert content == b"Cavefish have no sight."
+
+ @pytest.mark.asyncio
+ async def test_response_content_raises_error(self, mock_response):
+ with patch.object(
+ mock_response._response.content,
+ "iter_chunked",
+ side_effect=aiohttp.ClientPayloadError,
+ ):
+ with pytest.raises(exceptions.ResponseError) as exc:
+ [chunk async for chunk in mock_response.content()]
+ exc.match("Failed to read from the payload stream")
+
+ @pytest.mark.asyncio
+ async def test_response_read(self, mock_response):
+ content = await mock_response.read()
+ assert content == b"Cavefish have no sight."
+
+ @pytest.mark.asyncio
+ async def test_response_read_raises_error(self, mock_response):
+ with patch.object(
+ mock_response._response,
+ "read",
+ side_effect=aiohttp.ClientResponseError(None, None),
+ ):
+ with pytest.raises(exceptions.ResponseError) as exc:
+ await mock_response.read()
+ exc.match("Failed to read the response body.")
+
+ @pytest.mark.asyncio
+ async def test_response_close(self, mock_response):
+ await mock_response.close()
+ mock_response._response.close.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_response_content_stream(self, mock_response):
+ itr = mock_response.content().__aiter__()
+ content = []
+ try:
+ while True:
+ chunk = await itr.__anext__()
+ content.append(chunk)
+ except StopAsyncIteration:
+ pass
+ assert b"".join(content) == b"Cavefish have no sight."
+
+
+@pytest.mark.asyncio
+class TestRequest:
+ @pytest_asyncio.fixture
+ async def aiohttp_request(self):
+ request = auth_aiohttp.Request()
+ yield request
+ await request.close()
+
+ async def test_request_call_success(self, aiohttp_request):
+ with aioresponses() as m:
+ mocked_chunks = [b"Cavefish ", b"have ", b"no ", b"sight."]
+ mocked_response = b"".join(mocked_chunks)
+ m.get("http://example.com", status=200, body=mocked_response)
+ response = await aiohttp_request("http://example.com")
+ assert response.status_code == 200
+ assert response.headers == {"Content-Type": "application/json"}
+ content = b"".join([chunk async for chunk in response.content()])
+ assert content == b"Cavefish have no sight."
+
+ async def test_request_call_success_with_provided_session(self):
+ mock_session = aiohttp.ClientSession()
+ request = auth_aiohttp.Request(mock_session)
+ with aioresponses() as m:
+ mocked_chunks = [b"Cavefish ", b"have ", b"no ", b"sight."]
+ mocked_response = b"".join(mocked_chunks)
+ m.get("http://example.com", status=200, body=mocked_response)
+ response = await request("http://example.com")
+ assert response.status_code == 200
+ assert response.headers == {"Content-Type": "application/json"}
+ content = b"".join([chunk async for chunk in response.content()])
+ assert content == b"Cavefish have no sight."
+
+ async def test_request_call_raises_client_error(self, aiohttp_request):
+ with aioresponses() as m:
+ m.get("http://example.com", exception=aiohttp.ClientError)
+
+ with pytest.raises(exceptions.TransportError) as exc:
+ await aiohttp_request("http://example.com/api")
+
+ exc.match("Failed to send request to http://example.com/api.")
+
+ async def test_request_call_raises_timeout_error(self, aiohttp_request):
+ with aioresponses() as m:
+ m.get("http://example.com", exception=asyncio.TimeoutError)
+
+ with pytest.raises(exceptions.TimeoutError) as exc:
+ await aiohttp_request("http://example.com")
+
+ exc.match("Request timed out after 180 seconds.")
+
+ async def test_request_call_raises_transport_error_for_closed_session(
+ self, aiohttp_request
+ ):
+ with aioresponses() as m:
+ m.get("http://example.com", exception=asyncio.TimeoutError)
+ aiohttp_request._closed = True
+ with pytest.raises(exceptions.TransportError) as exc:
+ await aiohttp_request("http://example.com")
+
+ exc.match("session is closed.")
+ aiohttp_request._closed = False
diff --git a/contrib/python/google-auth/py3/tests/transport/aio/test_sessions.py b/contrib/python/google-auth/py3/tests/transport/aio/test_sessions.py
new file mode 100644
index 00000000000..c91a7c40aeb
--- /dev/null
+++ b/contrib/python/google-auth/py3/tests/transport/aio/test_sessions.py
@@ -0,0 +1,311 @@
+# Copyright 2024 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 asyncio
+from typing import AsyncGenerator
+
+from aioresponses import aioresponses # type: ignore
+from mock import Mock, patch
+import pytest # type: ignore
+
+from google.auth.aio.credentials import AnonymousCredentials
+from google.auth.aio.transport import (
+ _DEFAULT_TIMEOUT_SECONDS,
+ DEFAULT_MAX_RETRY_ATTEMPTS,
+ DEFAULT_RETRYABLE_STATUS_CODES,
+ Request,
+ Response,
+ sessions,
+)
+from google.auth.exceptions import InvalidType, TimeoutError, TransportError
+
+
+@pytest.fixture
+async def simple_async_task():
+ return True
+
+
+class MockRequest(Request):
+ def __init__(self, response=None, side_effect=None):
+ self._closed = False
+ self._response = response
+ self._side_effect = side_effect
+ self.call_count = 0
+
+ async def __call__(
+ self,
+ url,
+ method="GET",
+ body=None,
+ headers=None,
+ timeout=_DEFAULT_TIMEOUT_SECONDS,
+ **kwargs,
+ ):
+ self.call_count += 1
+ if self._side_effect:
+ raise self._side_effect
+ return self._response
+
+ async def close(self):
+ self._closed = True
+ return None
+
+
+class MockResponse(Response):
+ def __init__(self, status_code, headers=None, content=None):
+ self._status_code = status_code
+ self._headers = headers
+ self._content = content
+ self._close = False
+
+ @property
+ def status_code(self):
+ return self._status_code
+
+ @property
+ def headers(self):
+ return self._headers
+
+ async def read(self) -> bytes:
+ content = await self.content(1024)
+ return b"".join([chunk async for chunk in content])
+
+ async def content(self, chunk_size=None) -> AsyncGenerator:
+ return self._content
+
+ async def close(self) -> None:
+ self._close = True
+
+
+class TestTimeoutGuard(object):
+ default_timeout = 1
+
+ def make_timeout_guard(self, timeout):
+ return sessions.timeout_guard(timeout)
+
+ @pytest.mark.asyncio
+ async def test_timeout_with_simple_async_task_within_bounds(
+ self, simple_async_task
+ ):
+ task = False
+ with patch("time.monotonic", side_effect=[0, 0.25, 0.75]):
+ with patch("asyncio.wait_for", lambda coro, _: coro):
+ async with self.make_timeout_guard(
+ timeout=self.default_timeout
+ ) as with_timeout:
+ task = await with_timeout(simple_async_task)
+
+ # Task succeeds.
+ assert task is True
+
+ @pytest.mark.asyncio
+ async def test_timeout_with_simple_async_task_out_of_bounds(
+ self, simple_async_task
+ ):
+ task = False
+ with patch("time.monotonic", side_effect=[0, 1, 1]):
+ with pytest.raises(TimeoutError) as exc:
+ async with self.make_timeout_guard(
+ timeout=self.default_timeout
+ ) as with_timeout:
+ task = await with_timeout(simple_async_task)
+
+ # Task does not succeed and the context manager times out i.e. no remaining time left.
+ assert task is False
+ assert exc.match(
+ f"Context manager exceeded the configured timeout of {self.default_timeout}s."
+ )
+
+ @pytest.mark.asyncio
+ async def test_timeout_with_async_task_timing_out_before_context(
+ self, simple_async_task
+ ):
+ task = False
+ with pytest.raises(TimeoutError) as exc:
+ async with self.make_timeout_guard(
+ timeout=self.default_timeout
+ ) as with_timeout:
+ with patch("asyncio.wait_for", side_effect=asyncio.TimeoutError):
+ task = await with_timeout(simple_async_task)
+
+ # Task does not complete i.e. the operation times out.
+ assert task is False
+ assert exc.match(
+ f"The operation {simple_async_task} exceeded the configured timeout of {self.default_timeout}s."
+ )
+
+
+class TestAsyncAuthorizedSession(object):
+ TEST_URL = "http://example.com/"
+ credentials = AnonymousCredentials()
+
+ @pytest.fixture
+ async def mocked_content(self):
+ content = [b"Cavefish ", b"have ", b"no ", b"sight."]
+ for chunk in content:
+ yield chunk
+
+ @pytest.mark.asyncio
+ async def test_constructor_with_default_auth_request(self):
+ with patch("google.auth.aio.transport.sessions.AIOHTTP_INSTALLED", True):
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials)
+ assert authed_session._credentials == self.credentials
+ await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_constructor_with_provided_auth_request(self):
+ auth_request = MockRequest()
+ authed_session = sessions.AsyncAuthorizedSession(
+ self.credentials, auth_request=auth_request
+ )
+
+ assert authed_session._auth_request is auth_request
+ await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_constructor_raises_no_auth_request_error(self):
+ with patch("google.auth.aio.transport.sessions.AIOHTTP_INSTALLED", False):
+ with pytest.raises(TransportError) as exc:
+ sessions.AsyncAuthorizedSession(self.credentials)
+
+ exc.match(
+ "`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value."
+ )
+
+ @pytest.mark.asyncio
+ async def test_constructor_raises_incorrect_credentials_error(self):
+ credentials = Mock()
+ with pytest.raises(InvalidType) as exc:
+ sessions.AsyncAuthorizedSession(credentials)
+
+ exc.match(
+ f"The configured credentials of type {type(credentials)} are invalid and must be of type `google.auth.aio.credentials.Credentials`"
+ )
+
+ @pytest.mark.asyncio
+ async def test_request_default_auth_request_success(self):
+ with aioresponses() as m:
+ mocked_chunks = [b"Cavefish ", b"have ", b"no ", b"sight."]
+ mocked_response = b"".join(mocked_chunks)
+ m.get(self.TEST_URL, status=200, body=mocked_response)
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials)
+ response = await authed_session.request("GET", self.TEST_URL)
+ assert response.status_code == 200
+ assert response.headers == {"Content-Type": "application/json"}
+ assert await response.read() == b"Cavefish have no sight."
+ await response.close()
+
+ await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_request_provided_auth_request_success(self, mocked_content):
+ mocked_response = MockResponse(
+ status_code=200,
+ headers={"Content-Type": "application/json"},
+ content=mocked_content,
+ )
+ auth_request = MockRequest(mocked_response)
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request)
+ response = await authed_session.request("GET", self.TEST_URL)
+ assert response.status_code == 200
+ assert response.headers == {"Content-Type": "application/json"}
+ assert await response.read() == b"Cavefish have no sight."
+ await response.close()
+ assert response._close
+
+ await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_request_raises_timeout_error(self):
+ auth_request = MockRequest(side_effect=asyncio.TimeoutError)
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request)
+ with pytest.raises(TimeoutError):
+ await authed_session.request("GET", self.TEST_URL)
+
+ @pytest.mark.asyncio
+ async def test_request_raises_transport_error(self):
+ auth_request = MockRequest(side_effect=TransportError)
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request)
+ with pytest.raises(TransportError):
+ await authed_session.request("GET", self.TEST_URL)
+
+ @pytest.mark.asyncio
+ async def test_request_max_allowed_time_exceeded_error(self):
+ auth_request = MockRequest(side_effect=TransportError)
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request)
+ with patch("time.monotonic", side_effect=[0, 1, 1]):
+ with pytest.raises(TimeoutError):
+ await authed_session.request("GET", self.TEST_URL, max_allowed_time=1)
+
+ @pytest.mark.parametrize("retry_status", DEFAULT_RETRYABLE_STATUS_CODES)
+ @pytest.mark.asyncio
+ async def test_request_max_retries(self, retry_status):
+ mocked_response = MockResponse(status_code=retry_status)
+ auth_request = MockRequest(mocked_response)
+ with patch("asyncio.sleep", return_value=None):
+ authed_session = sessions.AsyncAuthorizedSession(
+ self.credentials, auth_request
+ )
+ await authed_session.request("GET", self.TEST_URL)
+ assert auth_request.call_count == DEFAULT_MAX_RETRY_ATTEMPTS
+
+ @pytest.mark.asyncio
+ async def test_http_get_method_success(self):
+ expected_payload = b"content is retrieved."
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials)
+ with aioresponses() as m:
+ m.get(self.TEST_URL, status=200, body=expected_payload)
+ response = await authed_session.get(self.TEST_URL)
+ assert await response.read() == expected_payload
+ response = await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_http_post_method_success(self):
+ expected_payload = b"content is posted."
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials)
+ with aioresponses() as m:
+ m.post(self.TEST_URL, status=200, body=expected_payload)
+ response = await authed_session.post(self.TEST_URL)
+ assert await response.read() == expected_payload
+ response = await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_http_put_method_success(self):
+ expected_payload = b"content is retrieved."
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials)
+ with aioresponses() as m:
+ m.put(self.TEST_URL, status=200, body=expected_payload)
+ response = await authed_session.put(self.TEST_URL)
+ assert await response.read() == expected_payload
+ response = await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_http_patch_method_success(self):
+ expected_payload = b"content is retrieved."
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials)
+ with aioresponses() as m:
+ m.patch(self.TEST_URL, status=200, body=expected_payload)
+ response = await authed_session.patch(self.TEST_URL)
+ assert await response.read() == expected_payload
+ response = await authed_session.close()
+
+ @pytest.mark.asyncio
+ async def test_http_delete_method_success(self):
+ expected_payload = b"content is deleted."
+ authed_session = sessions.AsyncAuthorizedSession(self.credentials)
+ with aioresponses() as m:
+ m.delete(self.TEST_URL, status=200, body=expected_payload)
+ response = await authed_session.delete(self.TEST_URL)
+ assert await response.read() == expected_payload
+ response = await authed_session.close()
diff --git a/contrib/python/google-auth/py3/tests/ya.make b/contrib/python/google-auth/py3/tests/ya.make
index 6c6db898c40..23e821bb9ae 100644
--- a/contrib/python/google-auth/py3/tests/ya.make
+++ b/contrib/python/google-auth/py3/tests/ya.make
@@ -9,6 +9,8 @@ PEERDIR(
contrib/python/pytest-localserver
contrib/python/oauth2client
contrib/python/freezegun
+ contrib/python/aioresponses
+ contrib/python/pytest-asyncio
)
DATA(
@@ -22,16 +24,16 @@ PY_SRCS(
)
TEST_SRCS(
- __init__.py
compute_engine/__init__.py
- compute_engine/test__metadata.py
compute_engine/test_credentials.py
+ compute_engine/test__metadata.py
conftest.py
crypt/__init__.py
crypt/test__cryptography_rsa.py
- crypt/test__python_rsa.py
crypt/test_crypt.py
crypt/test_es256.py
+ crypt/test__python_rsa.py
+ __init__.py
oauth2/__init__.py
oauth2/test__client.py
# oauth2/test_challenges.py - need pyu2f
@@ -42,35 +44,38 @@ TEST_SRCS(
oauth2/test_service_account.py
oauth2/test_sts.py
oauth2/test_utils.py
- oauth2/test_webauthn_handler.py
oauth2/test_webauthn_handler_factory.py
+ oauth2/test_webauthn_handler.py
oauth2/test_webauthn_types.py
- test__cloud_sdk.py
- test__default.py
- test__exponential_backoff.py
- test__helpers.py
- test__oauth2client.py
- test__refresh_worker.py
- test__service_account_info.py
test_api_key.py
test_app_engine.py
test_aws.py
+ test__cloud_sdk.py
+ test_credentials_async.py
test_credentials.py
+ test__default.py
test_downscoped.py
test_exceptions.py
- test_external_account.py
+ test__exponential_backoff.py
test_external_account_authorized_user.py
+ test_external_account.py
+ test__helpers.py
test_iam.py
test_identity_pool.py
test_impersonated_credentials.py
test_jwt.py
test_metrics.py
+ test__oauth2client.py
test_packaging.py
test_pluggable.py
+ test__refresh_worker.py
+ test__service_account_info.py
+ transport/aio/test_aiohttp.py
+ # transport/aio/test_sessions.py
# transport/test__custom_tls_signer.py
+ transport/test_grpc.py
transport/test__http_client.py
transport/test__mtls_helper.py
- transport/test_grpc.py
transport/test_mtls.py
# transport/test_requests.py
# transport/test_urllib3.py