From f36cafab5c999d827928ffcd98c43d4f51f8a5cf Mon Sep 17 00:00:00 2001 From: jkyle109 Date: Thu, 4 Aug 2022 16:59:24 -0400 Subject: [PATCH 1/2] Unit tests for async credentials. --- tests/test_credentials.py | 164 ++++++++++++++++++++++++++++++++++++-- tests/testutils.py | 54 +++++++++++++ 2 files changed, 211 insertions(+), 7 deletions(-) diff --git a/tests/test_credentials.py b/tests/test_credentials.py index cceb6b6f9..199ef13fd 100644 --- a/tests/test_credentials.py +++ b/tests/test_credentials.py @@ -22,7 +22,9 @@ from google.auth import crypt from google.auth import exceptions from google.oauth2 import credentials as gcredentials +from google.oauth2 import _credentials_async as gcredentials_async from google.oauth2 import service_account +from google.oauth2 import _service_account_async as service_account_async import pytest from firebase_admin import credentials @@ -33,16 +35,15 @@ def check_scopes(g_credential): assert isinstance(g_credential, google.auth.credentials.ReadOnlyScoped) assert sorted(credentials._scopes) == sorted(g_credential.scopes) +invalid_certs = { + 'NonExistingFile': ('non_existing.json', IOError), + 'RefreskToken': ('refresh_token.json', ValueError), + 'MalformedPrivateKey': ('malformed_key.json', ValueError), + 'MissingClientId': ('no_client_email_service_account.json', ValueError), +} class TestCertificate: - invalid_certs = { - 'NonExistingFile': ('non_existing.json', IOError), - 'RefreskToken': ('refresh_token.json', ValueError), - 'MalformedPrivateKey': ('malformed_key.json', ValueError), - 'MissingClientId': ('no_client_email_service_account.json', ValueError), - } - def test_init_from_file(self): credential = credentials.Certificate( testutils.resource_filename('service_account.json')) @@ -86,6 +87,55 @@ def _verify_credential(self, credential): assert isinstance(access_token.expiry, datetime.datetime) +class TestCertificateAsync: + + @pytest.mark.asyncio + async def test_init_from_file(self): + credential = credentials.Certificate( + testutils.resource_filename('service_account.json')) + await self._verify_credential(credential) + + @pytest.mark.asyncio + async def test_init_from_path_like(self): + path = pathlib.Path(testutils.resource_filename('service_account.json')) + credential = credentials.Certificate(path) + await self._verify_credential(credential) + + + @pytest.mark.asyncio + async def test_init_from_dict(self): + parsed_json = json.loads(testutils.resource('service_account.json')) + credential = credentials.Certificate(parsed_json) + await self._verify_credential(credential) + + @pytest.mark.parametrize('file_name,error', invalid_certs.values(), ids=list(invalid_certs)) + def test_init_from_invalid_certificate(self, file_name, error): + with pytest.raises(error): + credentials.Certificate(testutils.resource_filename(file_name)) + + @pytest.mark.parametrize('arg', [None, 0, 1, True, False, list(), tuple(), dict()]) + def test_invalid_args(self, arg): + with pytest.raises(ValueError): + credentials.Certificate(arg) + + @pytest.mark.asyncio + async def _verify_credential(self, credential): + assert credential.project_id == 'mock-project-id' + assert credential.service_account_email == 'mock-email@mock-project.iam.gserviceaccount.com' + assert isinstance(credential.signer, crypt.Signer) + + g_credential_async = credential.get_credential_async() + assert isinstance(g_credential_async, service_account_async.Credentials) + assert g_credential_async.token is None + check_scopes(g_credential_async) + + mock_response = {'access_token': 'mock_access_token', 'expires_in': 3600} + credentials._request_async = testutils.MockAsyncRequest(200, json.dumps(mock_response)) + access_token_async = await credential.get_access_token_async() + assert access_token_async.access_token == 'mock_access_token' + assert isinstance(access_token_async.expiry, datetime.datetime) + + @pytest.fixture def app_default(request): var_name = 'GOOGLE_APPLICATION_CREDENTIALS' @@ -129,6 +179,38 @@ def test_nonexisting_path(self, app_default): creds.get_credential() # This now throws. +class TestApplicationDefaultAsync: + + @pytest.mark.asyncio + @pytest.mark.parametrize('app_default', [testutils.resource_filename('service_account.json')], + indirect=True) + async def test_init(self, app_default): + del app_default + credential = credentials.ApplicationDefault() + assert credential.project_id == 'mock-project-id' + + g_credential_async = credential.get_credential_async() + assert isinstance(g_credential_async, google.auth.credentials.Credentials) + assert g_credential_async.token is None + check_scopes(g_credential_async) + + mock_response = {'access_token': 'mock_access_token', 'expires_in': 3600} + credentials._request_async = testutils.MockAsyncRequest(200, json.dumps(mock_response)) + access_token_async = await credential.get_access_token_async() + assert access_token_async.access_token == 'mock_access_token' + assert isinstance(access_token_async.expiry, datetime.datetime) + + @pytest.mark.parametrize('app_default', [testutils.resource_filename('non_existing.json')], + indirect=True) + def test_nonexisting_path(self, app_default): + del app_default + # This does not yet throw because the credentials are lazily loaded. + creds = credentials.ApplicationDefault() + + with pytest.raises(exceptions.DefaultCredentialsError): + creds.get_credential_async() # This now throws. + + class TestRefreshToken: def test_init_from_file(self): @@ -191,3 +273,71 @@ def _verify_credential(self, credential): access_token = credential.get_access_token() assert access_token.access_token == 'mock_access_token' assert isinstance(access_token.expiry, datetime.datetime) + + +class TestRefreshTokenAsync: + + @pytest.mark.asyncio + async def test_init_from_file(self): + credential = credentials.RefreshToken( + testutils.resource_filename('refresh_token.json')) + await self._verify_credential(credential) + + @pytest.mark.asyncio + async def test_init_from_path_like(self): + path = pathlib.Path(testutils.resource_filename('refresh_token.json')) + credential = credentials.RefreshToken(path) + await self._verify_credential(credential) + + @pytest.mark.asyncio + async def test_init_from_dict(self): + parsed_json = json.loads(testutils.resource('refresh_token.json')) + credential = credentials.RefreshToken(parsed_json) + await self._verify_credential(credential) + + def test_init_from_nonexisting_file(self): + with pytest.raises(IOError): + credentials.RefreshToken( + testutils.resource_filename('non_existing.json')) + + def test_init_from_invalid_file(self): + with pytest.raises(ValueError): + credentials.RefreshToken( + testutils.resource_filename('service_account.json')) + + @pytest.mark.parametrize('arg', [None, 0, 1, True, False, list(), tuple(), dict()]) + def test_invalid_args(self, arg): + with pytest.raises(ValueError): + credentials.RefreshToken(arg) + + @pytest.mark.parametrize('key', ['client_id', 'client_secret', 'refresh_token']) + def test_required_field(self, key): + data = { + 'client_id': 'value', + 'client_secret': 'value', + 'refresh_token': 'value', + 'type': 'authorized_user' + } + del data[key] + with pytest.raises(ValueError): + credentials.RefreshToken(data) + + @pytest.mark.asyncio + async def _verify_credential(self, credential): + assert credential.client_id == 'mock.apps.googleusercontent.com' + assert credential.client_secret == 'mock-secret' + assert credential.refresh_token == 'mock-refresh-token' + + g_credential_async = credential.get_credential_async() + assert isinstance(g_credential_async, gcredentials_async.Credentials) + assert g_credential_async.token is None + check_scopes(g_credential_async) + + mock_response = { + 'access_token': 'mock_access_token', + 'expires_in': 3600 + } + credentials._request_async = testutils.MockAsyncRequest(200, json.dumps(mock_response)) + access_token_async = await credential.get_access_token_async() + assert access_token_async.access_token == 'mock_access_token' + assert isinstance(access_token_async.expiry, datetime.datetime) diff --git a/tests/testutils.py b/tests/testutils.py index 92755107c..6ab69dda4 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -20,6 +20,7 @@ from google.auth import credentials from google.auth import transport +from google.auth.transport import _aiohttp_requests as aiohttp_requests from requests import adapters from requests import models @@ -111,6 +112,59 @@ def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ raise self.error +class MockAsyncResponse(aiohttp_requests._CombinedResponse): + def __init__(self, status, response): + super(MockAsyncResponse, self).__init__(response) + self._status = status + self._response = response + self._raw_content = response + + @property + def status(self): + return self._status + + @property + def headers(self): + return {} + + @property + def data(self): + return self._response.encode() + + async def content(self): + return self._response.encode() + +class MockAsyncRequest(aiohttp_requests.Request): + """A mock async HTTP requests implementation. + + This can be used whenever an async HTTP interaction needs to be mocked + for testing purposes. For example HTTP calls to fetch public key + certificates, and HTTP calls to retrieve access tokens can be + mocked using this class. + """ + + def __init__(self, status, response): + super(MockAsyncRequest, self).__init__() + self.response = MockAsyncResponse(status, response) + self.log = [] + + async def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ + self.log.append((args, kwargs)) + return self.response + + +class MockFailedAsyncRequest(aiohttp_requests.Request): + """A mock HTTP request that fails by raising an exception.""" + + def __init__(self, error): + super(MockFailedAsyncRequest, self).__init__() + self.error = error + self.log = [] + + async def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ + self.log.append((args, kwargs)) + raise self.error + # Temporarily disable the lint rule. For more information see: # https://github.com/googleapis/google-auth-library-python/pull/561 # pylint: disable=abstract-method From 351294593f6290571e4cfc4a2b47cf428a562198 Mon Sep 17 00:00:00 2001 From: jkyle109 Date: Thu, 4 Aug 2022 17:17:21 -0400 Subject: [PATCH 2/2] fix: Removed redundant tests. --- tests/test_credentials.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/tests/test_credentials.py b/tests/test_credentials.py index 199ef13fd..733f45dea 100644 --- a/tests/test_credentials.py +++ b/tests/test_credentials.py @@ -108,16 +108,6 @@ async def test_init_from_dict(self): credential = credentials.Certificate(parsed_json) await self._verify_credential(credential) - @pytest.mark.parametrize('file_name,error', invalid_certs.values(), ids=list(invalid_certs)) - def test_init_from_invalid_certificate(self, file_name, error): - with pytest.raises(error): - credentials.Certificate(testutils.resource_filename(file_name)) - - @pytest.mark.parametrize('arg', [None, 0, 1, True, False, list(), tuple(), dict()]) - def test_invalid_args(self, arg): - with pytest.raises(ValueError): - credentials.Certificate(arg) - @pytest.mark.asyncio async def _verify_credential(self, credential): assert credential.project_id == 'mock-project-id' @@ -295,33 +285,6 @@ async def test_init_from_dict(self): credential = credentials.RefreshToken(parsed_json) await self._verify_credential(credential) - def test_init_from_nonexisting_file(self): - with pytest.raises(IOError): - credentials.RefreshToken( - testutils.resource_filename('non_existing.json')) - - def test_init_from_invalid_file(self): - with pytest.raises(ValueError): - credentials.RefreshToken( - testutils.resource_filename('service_account.json')) - - @pytest.mark.parametrize('arg', [None, 0, 1, True, False, list(), tuple(), dict()]) - def test_invalid_args(self, arg): - with pytest.raises(ValueError): - credentials.RefreshToken(arg) - - @pytest.mark.parametrize('key', ['client_id', 'client_secret', 'refresh_token']) - def test_required_field(self, key): - data = { - 'client_id': 'value', - 'client_secret': 'value', - 'refresh_token': 'value', - 'type': 'authorized_user' - } - del data[key] - with pytest.raises(ValueError): - credentials.RefreshToken(data) - @pytest.mark.asyncio async def _verify_credential(self, credential): assert credential.client_id == 'mock.apps.googleusercontent.com'