Skip to content

Commit e753cc4

Browse files
authored
TST: Add unit tests for pandas_gbq.auth.get_credentials(). (googleapis#184)
* TST: Add unit tests for pandas_gbq.auth.get_credentials(). Tests (actual auth mocked out): * Using private key with contents. * Using private key with path. * Using default credentials. * Using cached user credentials. * TST: Use classmethod decorator for mocked methods. See: https://stackoverflow.com/a/29235090/101923
1 parent ee9955f commit e753cc4

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

pandas_gbq/auth.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ def get_service_account_credentials(private_key):
3939
import google.auth.transport.requests
4040
from google.oauth2.service_account import Credentials
4141

42+
is_path = os.path.isfile(private_key)
43+
4244
try:
43-
if os.path.isfile(private_key):
45+
if is_path:
4446
with open(private_key) as f:
4547
json_key = json.loads(f.read())
4648
else:
@@ -64,11 +66,13 @@ def get_service_account_credentials(private_key):
6466
return credentials, json_key.get('project_id')
6567
except (KeyError, ValueError, TypeError, AttributeError):
6668
raise pandas_gbq.exceptions.InvalidPrivateKeyFormat(
67-
"Private key is missing or invalid. It should be service "
68-
"account private key JSON (file path or string contents) "
69-
"with at least two keys: 'client_email' and 'private_key'. "
70-
"Can be obtained from: https://console.developers.google."
71-
"com/permissions/serviceaccounts")
69+
'Detected private_key as {}. '.format(
70+
'path' if is_path else 'contents') +
71+
'Private key is missing or invalid. It should be service '
72+
'account private key JSON (file path or string contents) '
73+
'with at least two keys: "client_email" and "private_key". '
74+
'Can be obtained from: https://console.developers.google.'
75+
'com/permissions/serviceaccounts')
7276

7377

7478
def get_application_default_credentials(project_id=None):

tests/data/dummy_key.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
{
3+
"private_key": "some_key",
4+
"client_email": "[email protected]"
5+
}

tests/unit/test_auth.py

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import json
4+
import os.path
5+
6+
try:
7+
import mock
8+
except ImportError: # pragma: NO COVER
9+
from unittest import mock
10+
11+
from pandas_gbq import auth
12+
13+
14+
def test_get_credentials_private_key_contents(monkeypatch):
15+
from google.oauth2 import service_account
16+
17+
@classmethod
18+
def from_service_account_info(cls, key_info):
19+
mock_credentials = mock.create_autospec(cls)
20+
mock_credentials.with_scopes.return_value = mock_credentials
21+
mock_credentials.refresh.return_value = mock_credentials
22+
return mock_credentials
23+
24+
monkeypatch.setattr(
25+
service_account.Credentials,
26+
'from_service_account_info',
27+
from_service_account_info)
28+
private_key = json.dumps({
29+
'private_key': 'some_key',
30+
'client_email': '[email protected]',
31+
'project_id': 'private-key-project'
32+
})
33+
credentials, project = auth.get_credentials(private_key=private_key)
34+
35+
assert credentials is not None
36+
assert project == 'private-key-project'
37+
38+
39+
def test_get_credentials_private_key_path(monkeypatch):
40+
from google.oauth2 import service_account
41+
42+
@classmethod
43+
def from_service_account_info(cls, key_info):
44+
mock_credentials = mock.create_autospec(cls)
45+
mock_credentials.with_scopes.return_value = mock_credentials
46+
mock_credentials.refresh.return_value = mock_credentials
47+
return mock_credentials
48+
49+
monkeypatch.setattr(
50+
service_account.Credentials,
51+
'from_service_account_info',
52+
from_service_account_info)
53+
private_key = os.path.join(
54+
os.path.dirname(__file__), '..', 'data', 'dummy_key.json')
55+
credentials, project = auth.get_credentials(private_key=private_key)
56+
57+
assert credentials is not None
58+
assert project is None
59+
60+
61+
def test_get_credentials_default_credentials(monkeypatch):
62+
import google.auth
63+
import google.auth.credentials
64+
import google.cloud.bigquery
65+
66+
def mock_default_credentials(scopes=None, request=None):
67+
return (
68+
mock.create_autospec(google.auth.credentials.Credentials),
69+
'default-project',
70+
)
71+
72+
monkeypatch.setattr(google.auth, 'default', mock_default_credentials)
73+
mock_client = mock.create_autospec(google.cloud.bigquery.Client)
74+
monkeypatch.setattr(google.cloud.bigquery, 'Client', mock_client)
75+
76+
credentials, project = auth.get_credentials()
77+
assert project == 'default-project'
78+
assert credentials is not None
79+
80+
81+
def test_get_credentials_load_user_no_default(monkeypatch):
82+
import google.auth
83+
import google.auth.credentials
84+
85+
def mock_default_credentials(scopes=None, request=None):
86+
return (None, None)
87+
88+
monkeypatch.setattr(google.auth, 'default', mock_default_credentials)
89+
mock_user_credentials = mock.create_autospec(
90+
google.auth.credentials.Credentials)
91+
92+
def mock_load_credentials(project_id=None, credentials_path=None):
93+
return mock_user_credentials
94+
95+
monkeypatch.setattr(
96+
auth,
97+
'load_user_account_credentials',
98+
mock_load_credentials)
99+
100+
credentials, project = auth.get_credentials()
101+
assert project is None
102+
assert credentials is mock_user_credentials

0 commit comments

Comments
 (0)