Skip to content

Commit fffede8

Browse files
committed
Adding test coverage for client.py.
- `DeviceFlowInfo` (and using datetime now patch). - Corner cases for `step1_get_authorize_url` and `step2_exchange` - Failure cases for `flow_from_clientsecrets` - `verify_id_token` when the cached HTTP is used (i.e. the default argument)
1 parent 3ff5810 commit fffede8

File tree

3 files changed

+167
-2
lines changed

3 files changed

+167
-2
lines changed

oauth2client/client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
# Expose utcnow() at module level to allow for
121121
# easier testing (by replacing with a stub).
122122
_UTCNOW = datetime.datetime.utcnow
123+
_NOW = datetime.datetime.now
123124

124125

125126
class SETTINGS(object):
@@ -1862,7 +1863,7 @@ def FromResponse(cls, response):
18621863
})
18631864
if 'expires_in' in response:
18641865
kwargs['user_code_expiry'] = (
1865-
datetime.datetime.now() +
1866+
_NOW() +
18661867
datetime.timedelta(seconds=int(response['expires_in'])))
18671868
return cls(**kwargs)
18681869

@@ -2201,4 +2202,4 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
22012202
raise
22022203
else:
22032204
raise UnknownClientSecretsFlowError(
2204-
'This OAuth 2.0 flow is unsupported: %r' % client_type)
2205+
'This OAuth 2.0 flow is unsupported: %r' % (client_type,))

tests/test_client.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from oauth2client.client import AUTHORIZED_USER
5151
from oauth2client.client import Credentials
5252
from oauth2client.client import DEFAULT_ENV_NAME
53+
from oauth2client.client import DeviceFlowInfo
5354
from oauth2client.client import Error
5455
from oauth2client.client import ApplicationDefaultCredentialsError
5556
from oauth2client.client import FlowExchangeError
@@ -80,6 +81,7 @@
8081
from oauth2client.client import flow_from_clientsecrets
8182
from oauth2client.client import save_to_well_known_file
8283
from oauth2client.clientsecrets import _loadfile
84+
from oauth2client.clientsecrets import InvalidClientSecretsError
8385
from oauth2client.service_account import ServiceAccountCredentials
8486
from oauth2client._helpers import _to_bytes
8587

@@ -1695,6 +1697,60 @@ def test_override_flow_via_kwargs(self):
16951697
self.assertEqual(OOB_CALLBACK_URN, q['redirect_uri'][0])
16961698
self.assertEqual('online', q['access_type'][0])
16971699

1700+
@mock.patch('oauth2client.client.logger')
1701+
def test_step1_get_authorize_url_redirect_override(self, logger):
1702+
flow = OAuth2WebServerFlow('client_id+1', scope='foo',
1703+
redirect_uri=OOB_CALLBACK_URN)
1704+
alt_redirect = 'foo:bar'
1705+
self.assertEqual(flow.redirect_uri, OOB_CALLBACK_URN)
1706+
result = flow.step1_get_authorize_url(redirect_uri=alt_redirect)
1707+
# Make sure the redirect value was updated.
1708+
self.assertEqual(flow.redirect_uri, alt_redirect)
1709+
query_params = {
1710+
'client_id': flow.client_id,
1711+
'redirect_uri': alt_redirect,
1712+
'scope': flow.scope,
1713+
'access_type': 'offline',
1714+
'response_type': 'code',
1715+
}
1716+
expected = _update_query_params(flow.auth_uri, query_params)
1717+
assertUrisEqual(self, expected, result)
1718+
# Check stubs.
1719+
self.assertEqual(logger.warning.call_count, 1)
1720+
1721+
def test_step1_get_authorize_url_without_redirect(self):
1722+
flow = OAuth2WebServerFlow('client_id+1', scope='foo',
1723+
redirect_uri=None)
1724+
with self.assertRaises(ValueError):
1725+
flow.step1_get_authorize_url(redirect_uri=None)
1726+
1727+
def test_step1_get_authorize_url_without_login_hint(self):
1728+
login_hint = 'There are wascally wabbits nearby'
1729+
flow = OAuth2WebServerFlow('client_id+1', scope='foo',
1730+
redirect_uri=OOB_CALLBACK_URN,
1731+
login_hint=login_hint)
1732+
result = flow.step1_get_authorize_url()
1733+
query_params = {
1734+
'client_id': flow.client_id,
1735+
'login_hint': login_hint,
1736+
'redirect_uri': OOB_CALLBACK_URN,
1737+
'scope': flow.scope,
1738+
'access_type': 'offline',
1739+
'response_type': 'code',
1740+
}
1741+
expected = _update_query_params(flow.auth_uri, query_params)
1742+
assertUrisEqual(self, expected, result)
1743+
1744+
def test_step2_exchange_no_input(self):
1745+
flow = OAuth2WebServerFlow('client_id+1', scope='foo')
1746+
with self.assertRaises(ValueError):
1747+
flow.step2_exchange()
1748+
1749+
def test_step2_exchange_code_and_device_flow(self):
1750+
flow = OAuth2WebServerFlow('client_id+1', scope='foo')
1751+
with self.assertRaises(ValueError):
1752+
flow.step2_exchange(code='code', device_flow_info='dfi')
1753+
16981754
def test_scope_is_required(self):
16991755
self.assertRaises(TypeError, OAuth2WebServerFlow, 'client_id+1')
17001756

@@ -1910,6 +1966,43 @@ def test_flow_from_clientsecrets_cached(self):
19101966
'some_secrets', '', redirect_uri='oob', cache=cache_mock)
19111967
self.assertEqual('foo_client_secret', flow.client_secret)
19121968

1969+
@mock.patch('oauth2client.clientsecrets.loadfile',
1970+
side_effect=InvalidClientSecretsError)
1971+
def test_flow_from_clientsecrets_invalid(self, loadfile_mock):
1972+
filename = object()
1973+
cache = object()
1974+
with self.assertRaises(InvalidClientSecretsError):
1975+
flow_from_clientsecrets(filename, None, cache=cache,
1976+
message=None)
1977+
loadfile_mock.assert_called_once_with(filename, cache=cache)
1978+
1979+
@mock.patch('oauth2client.clientsecrets.loadfile',
1980+
side_effect=InvalidClientSecretsError)
1981+
@mock.patch('sys.exit')
1982+
def test_flow_from_clientsecrets_invalid_w_msg(self, sys_exit,
1983+
loadfile_mock):
1984+
filename = object()
1985+
cache = object()
1986+
message = 'hi mom'
1987+
1988+
flow_from_clientsecrets(filename, None, cache=cache, message=message)
1989+
sys_exit.assert_called_once_with(message)
1990+
loadfile_mock.assert_called_once_with(filename, cache=cache)
1991+
1992+
@mock.patch('oauth2client.clientsecrets.loadfile')
1993+
def test_flow_from_clientsecrets_unknown_flow(self, loadfile_mock):
1994+
client_type = 'UNKNOWN'
1995+
loadfile_mock.return_value = client_type, None
1996+
filename = object()
1997+
cache = object()
1998+
1999+
err_msg = 'This OAuth 2.0 flow is unsupported: %r' % (client_type,)
2000+
with self.assertRaisesRegexp(client.UnknownClientSecretsFlowError,
2001+
err_msg):
2002+
flow_from_clientsecrets(filename, None, cache=cache)
2003+
2004+
loadfile_mock.assert_called_once_with(filename, cache=cache)
2005+
19132006

19142007
class CredentialsFromCodeTests(unittest2.TestCase):
19152008

@@ -2061,5 +2154,60 @@ def test_without_crypto(self):
20612154
client._require_crypto_or_die()
20622155

20632156

2157+
class TestDeviceFlowInfo(unittest2.TestCase):
2158+
2159+
DEVICE_CODE = 'e80ff179-fd65-416c-9dbf-56a23e5d23e4'
2160+
USER_CODE = '4bbd8b82-fc73-11e5-adf3-00c2c63e5792'
2161+
VER_URL = 'http://foo.bar'
2162+
2163+
def test_FromResponse(self):
2164+
response = {
2165+
'device_code': self.DEVICE_CODE,
2166+
'user_code': self.USER_CODE,
2167+
'verification_url': self.VER_URL,
2168+
}
2169+
result = DeviceFlowInfo.FromResponse(response)
2170+
expected_result = DeviceFlowInfo(self.DEVICE_CODE, self.USER_CODE,
2171+
None, self.VER_URL, None)
2172+
self.assertEqual(result, expected_result)
2173+
2174+
def test_FromResponse_fallback_to_uri(self):
2175+
response = {
2176+
'device_code': self.DEVICE_CODE,
2177+
'user_code': self.USER_CODE,
2178+
'verification_uri': self.VER_URL,
2179+
}
2180+
result = DeviceFlowInfo.FromResponse(response)
2181+
expected_result = DeviceFlowInfo(self.DEVICE_CODE, self.USER_CODE,
2182+
None, self.VER_URL, None)
2183+
self.assertEqual(result, expected_result)
2184+
2185+
def test_FromResponse_missing_url(self):
2186+
response = {
2187+
'device_code': self.DEVICE_CODE,
2188+
'user_code': self.USER_CODE,
2189+
}
2190+
with self.assertRaises(client.OAuth2DeviceCodeError):
2191+
DeviceFlowInfo.FromResponse(response)
2192+
2193+
@mock.patch('oauth2client.client._NOW')
2194+
def test_FromResponse_with_expires_in(self, dt_now):
2195+
expires_in = 23
2196+
response = {
2197+
'device_code': self.DEVICE_CODE,
2198+
'user_code': self.USER_CODE,
2199+
'verification_url': self.VER_URL,
2200+
'expires_in': expires_in,
2201+
}
2202+
now = datetime.datetime(1999, 1, 1, 12, 30, 27)
2203+
expire = datetime.datetime(1999, 1, 1, 12, 30, 27 + expires_in)
2204+
dt_now.return_value = now
2205+
2206+
result = DeviceFlowInfo.FromResponse(response)
2207+
expected_result = DeviceFlowInfo(self.DEVICE_CODE, self.USER_CODE,
2208+
None, self.VER_URL, expire)
2209+
self.assertEqual(result, expected_result)
2210+
2211+
20642212
if __name__ == '__main__': # pragma: NO COVER
20652213
unittest2.main()

tests/test_jwt.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import os
1818
import tempfile
1919
import time
20+
21+
import mock
2022
import unittest2
2123

2224
from .http_mock import HttpMockSequence
@@ -129,6 +131,20 @@ def test_verify_id_token_with_certs_uri(self):
129131
self.assertEqual('billy bob', contents['user'])
130132
self.assertEqual('data', contents['metadata']['meta'])
131133

134+
def test_verify_id_token_with_certs_uri_default_http(self):
135+
jwt = self._create_signed_jwt()
136+
137+
http = HttpMockSequence([
138+
({'status': '200'}, datafile('certs.json')),
139+
])
140+
141+
with mock.patch('oauth2client.client._cached_http', new=http):
142+
contents = verify_id_token(
143+
144+
145+
self.assertEqual('billy bob', contents['user'])
146+
self.assertEqual('data', contents['metadata']['meta'])
147+
132148
def test_verify_id_token_with_certs_uri_fails(self):
133149
jwt = self._create_signed_jwt()
134150

0 commit comments

Comments
 (0)