Skip to content

Commit 8a809e7

Browse files
authored
Unit tests for async credentials. (#631)
* Unit tests for async credentials. * fix: Removed redundant tests.
1 parent f539137 commit 8a809e7

File tree

2 files changed

+174
-7
lines changed

2 files changed

+174
-7
lines changed

tests/test_credentials.py

Lines changed: 120 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
from google.auth import crypt
2323
from google.auth import exceptions
2424
from google.oauth2 import credentials as gcredentials
25+
from google.oauth2 import _credentials_async as gcredentials_async
2526
from google.oauth2 import service_account
27+
from google.oauth2 import _service_account_async as service_account_async
2628
import pytest
2729

2830
from firebase_admin import credentials
@@ -33,16 +35,15 @@ def check_scopes(g_credential):
3335
assert isinstance(g_credential, google.auth.credentials.ReadOnlyScoped)
3436
assert sorted(credentials._scopes) == sorted(g_credential.scopes)
3537

38+
invalid_certs = {
39+
'NonExistingFile': ('non_existing.json', IOError),
40+
'RefreskToken': ('refresh_token.json', ValueError),
41+
'MalformedPrivateKey': ('malformed_key.json', ValueError),
42+
'MissingClientId': ('no_client_email_service_account.json', ValueError),
43+
}
3644

3745
class TestCertificate:
3846

39-
invalid_certs = {
40-
'NonExistingFile': ('non_existing.json', IOError),
41-
'RefreskToken': ('refresh_token.json', ValueError),
42-
'MalformedPrivateKey': ('malformed_key.json', ValueError),
43-
'MissingClientId': ('no_client_email_service_account.json', ValueError),
44-
}
45-
4647
def test_init_from_file(self):
4748
credential = credentials.Certificate(
4849
testutils.resource_filename('service_account.json'))
@@ -86,6 +87,45 @@ def _verify_credential(self, credential):
8687
assert isinstance(access_token.expiry, datetime.datetime)
8788

8889

90+
class TestCertificateAsync:
91+
92+
@pytest.mark.asyncio
93+
async def test_init_from_file(self):
94+
credential = credentials.Certificate(
95+
testutils.resource_filename('service_account.json'))
96+
await self._verify_credential(credential)
97+
98+
@pytest.mark.asyncio
99+
async def test_init_from_path_like(self):
100+
path = pathlib.Path(testutils.resource_filename('service_account.json'))
101+
credential = credentials.Certificate(path)
102+
await self._verify_credential(credential)
103+
104+
105+
@pytest.mark.asyncio
106+
async def test_init_from_dict(self):
107+
parsed_json = json.loads(testutils.resource('service_account.json'))
108+
credential = credentials.Certificate(parsed_json)
109+
await self._verify_credential(credential)
110+
111+
@pytest.mark.asyncio
112+
async def _verify_credential(self, credential):
113+
assert credential.project_id == 'mock-project-id'
114+
assert credential.service_account_email == '[email protected]'
115+
assert isinstance(credential.signer, crypt.Signer)
116+
117+
g_credential_async = credential.get_credential_async()
118+
assert isinstance(g_credential_async, service_account_async.Credentials)
119+
assert g_credential_async.token is None
120+
check_scopes(g_credential_async)
121+
122+
mock_response = {'access_token': 'mock_access_token', 'expires_in': 3600}
123+
credentials._request_async = testutils.MockAsyncRequest(200, json.dumps(mock_response))
124+
access_token_async = await credential.get_access_token_async()
125+
assert access_token_async.access_token == 'mock_access_token'
126+
assert isinstance(access_token_async.expiry, datetime.datetime)
127+
128+
89129
@pytest.fixture
90130
def app_default(request):
91131
var_name = 'GOOGLE_APPLICATION_CREDENTIALS'
@@ -129,6 +169,38 @@ def test_nonexisting_path(self, app_default):
129169
creds.get_credential() # This now throws.
130170

131171

172+
class TestApplicationDefaultAsync:
173+
174+
@pytest.mark.asyncio
175+
@pytest.mark.parametrize('app_default', [testutils.resource_filename('service_account.json')],
176+
indirect=True)
177+
async def test_init(self, app_default):
178+
del app_default
179+
credential = credentials.ApplicationDefault()
180+
assert credential.project_id == 'mock-project-id'
181+
182+
g_credential_async = credential.get_credential_async()
183+
assert isinstance(g_credential_async, google.auth.credentials.Credentials)
184+
assert g_credential_async.token is None
185+
check_scopes(g_credential_async)
186+
187+
mock_response = {'access_token': 'mock_access_token', 'expires_in': 3600}
188+
credentials._request_async = testutils.MockAsyncRequest(200, json.dumps(mock_response))
189+
access_token_async = await credential.get_access_token_async()
190+
assert access_token_async.access_token == 'mock_access_token'
191+
assert isinstance(access_token_async.expiry, datetime.datetime)
192+
193+
@pytest.mark.parametrize('app_default', [testutils.resource_filename('non_existing.json')],
194+
indirect=True)
195+
def test_nonexisting_path(self, app_default):
196+
del app_default
197+
# This does not yet throw because the credentials are lazily loaded.
198+
creds = credentials.ApplicationDefault()
199+
200+
with pytest.raises(exceptions.DefaultCredentialsError):
201+
creds.get_credential_async() # This now throws.
202+
203+
132204
class TestRefreshToken:
133205

134206
def test_init_from_file(self):
@@ -191,3 +263,44 @@ def _verify_credential(self, credential):
191263
access_token = credential.get_access_token()
192264
assert access_token.access_token == 'mock_access_token'
193265
assert isinstance(access_token.expiry, datetime.datetime)
266+
267+
268+
class TestRefreshTokenAsync:
269+
270+
@pytest.mark.asyncio
271+
async def test_init_from_file(self):
272+
credential = credentials.RefreshToken(
273+
testutils.resource_filename('refresh_token.json'))
274+
await self._verify_credential(credential)
275+
276+
@pytest.mark.asyncio
277+
async def test_init_from_path_like(self):
278+
path = pathlib.Path(testutils.resource_filename('refresh_token.json'))
279+
credential = credentials.RefreshToken(path)
280+
await self._verify_credential(credential)
281+
282+
@pytest.mark.asyncio
283+
async def test_init_from_dict(self):
284+
parsed_json = json.loads(testutils.resource('refresh_token.json'))
285+
credential = credentials.RefreshToken(parsed_json)
286+
await self._verify_credential(credential)
287+
288+
@pytest.mark.asyncio
289+
async def _verify_credential(self, credential):
290+
assert credential.client_id == 'mock.apps.googleusercontent.com'
291+
assert credential.client_secret == 'mock-secret'
292+
assert credential.refresh_token == 'mock-refresh-token'
293+
294+
g_credential_async = credential.get_credential_async()
295+
assert isinstance(g_credential_async, gcredentials_async.Credentials)
296+
assert g_credential_async.token is None
297+
check_scopes(g_credential_async)
298+
299+
mock_response = {
300+
'access_token': 'mock_access_token',
301+
'expires_in': 3600
302+
}
303+
credentials._request_async = testutils.MockAsyncRequest(200, json.dumps(mock_response))
304+
access_token_async = await credential.get_access_token_async()
305+
assert access_token_async.access_token == 'mock_access_token'
306+
assert isinstance(access_token_async.expiry, datetime.datetime)

tests/testutils.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from google.auth import credentials
2222
from google.auth import transport
23+
from google.auth.transport import _aiohttp_requests as aiohttp_requests
2324
from requests import adapters
2425
from requests import models
2526

@@ -111,6 +112,59 @@ def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ
111112
raise self.error
112113

113114

115+
class MockAsyncResponse(aiohttp_requests._CombinedResponse):
116+
def __init__(self, status, response):
117+
super(MockAsyncResponse, self).__init__(response)
118+
self._status = status
119+
self._response = response
120+
self._raw_content = response
121+
122+
@property
123+
def status(self):
124+
return self._status
125+
126+
@property
127+
def headers(self):
128+
return {}
129+
130+
@property
131+
def data(self):
132+
return self._response.encode()
133+
134+
async def content(self):
135+
return self._response.encode()
136+
137+
class MockAsyncRequest(aiohttp_requests.Request):
138+
"""A mock async HTTP requests implementation.
139+
140+
This can be used whenever an async HTTP interaction needs to be mocked
141+
for testing purposes. For example HTTP calls to fetch public key
142+
certificates, and HTTP calls to retrieve access tokens can be
143+
mocked using this class.
144+
"""
145+
146+
def __init__(self, status, response):
147+
super(MockAsyncRequest, self).__init__()
148+
self.response = MockAsyncResponse(status, response)
149+
self.log = []
150+
151+
async def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ
152+
self.log.append((args, kwargs))
153+
return self.response
154+
155+
156+
class MockFailedAsyncRequest(aiohttp_requests.Request):
157+
"""A mock HTTP request that fails by raising an exception."""
158+
159+
def __init__(self, error):
160+
super(MockFailedAsyncRequest, self).__init__()
161+
self.error = error
162+
self.log = []
163+
164+
async def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ
165+
self.log.append((args, kwargs))
166+
raise self.error
167+
114168
# Temporarily disable the lint rule. For more information see:
115169
# https://github.com/googleapis/google-auth-library-python/pull/561
116170
# pylint: disable=abstract-method

0 commit comments

Comments
 (0)