100% coverage for contrib.appengine
This commit is contained in:
committed by
Jon Wayne Parrott
parent
5b280ec0a6
commit
3b1f6e596e
@@ -61,7 +61,7 @@ OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
|
|||||||
|
|
||||||
XSRF_MEMCACHE_ID = 'xsrf_secret_key'
|
XSRF_MEMCACHE_ID = 'xsrf_secret_key'
|
||||||
|
|
||||||
if _appengine_ndb is None:
|
if _appengine_ndb is None: # pragma: NO COVER
|
||||||
CredentialsNDBModel = None
|
CredentialsNDBModel = None
|
||||||
CredentialsNDBProperty = None
|
CredentialsNDBProperty = None
|
||||||
FlowNDBProperty = None
|
FlowNDBProperty = None
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest2
|
||||||
|
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
@@ -39,14 +39,20 @@ from google.appengine.ext import ndb
|
|||||||
from google.appengine.ext import testbed
|
from google.appengine.ext import testbed
|
||||||
from oauth2client.contrib import appengine
|
from oauth2client.contrib import appengine
|
||||||
from oauth2client import GOOGLE_TOKEN_URI
|
from oauth2client import GOOGLE_TOKEN_URI
|
||||||
|
from oauth2client import GOOGLE_REVOKE_URI
|
||||||
from oauth2client.clientsecrets import _loadfile
|
from oauth2client.clientsecrets import _loadfile
|
||||||
|
from oauth2client.clientsecrets import TYPE_WEB
|
||||||
from oauth2client.clientsecrets import InvalidClientSecretsError
|
from oauth2client.clientsecrets import InvalidClientSecretsError
|
||||||
from oauth2client.contrib.appengine import AppAssertionCredentials
|
from oauth2client.contrib.appengine import AppAssertionCredentials
|
||||||
from oauth2client.contrib.appengine import CredentialsModel
|
from oauth2client.contrib.appengine import CredentialsModel
|
||||||
from oauth2client.contrib.appengine import CredentialsNDBModel
|
from oauth2client.contrib.appengine import CredentialsNDBModel
|
||||||
|
from oauth2client.contrib.appengine import CredentialsProperty
|
||||||
from oauth2client.contrib.appengine import FlowProperty
|
from oauth2client.contrib.appengine import FlowProperty
|
||||||
|
from oauth2client.contrib.appengine import (
|
||||||
|
InvalidClientSecretsError as AppEngineInvalidClientSecretsError)
|
||||||
from oauth2client.contrib.appengine import OAuth2Decorator
|
from oauth2client.contrib.appengine import OAuth2Decorator
|
||||||
from oauth2client.contrib.appengine import OAuth2DecoratorFromClientSecrets
|
from oauth2client.contrib.appengine import OAuth2DecoratorFromClientSecrets
|
||||||
|
from oauth2client.contrib.appengine import oauth2decorator_from_clientsecrets
|
||||||
from oauth2client.contrib.appengine import StorageByKeyName
|
from oauth2client.contrib.appengine import StorageByKeyName
|
||||||
from oauth2client.client import _CLOUDSDK_CONFIG_ENV_VAR
|
from oauth2client.client import _CLOUDSDK_CONFIG_ENV_VAR
|
||||||
from oauth2client.client import AccessTokenRefreshError
|
from oauth2client.client import AccessTokenRefreshError
|
||||||
@@ -104,7 +110,7 @@ class Http2Mock(object):
|
|||||||
return self, json.dumps(self.content)
|
return self, json.dumps(self.content)
|
||||||
|
|
||||||
|
|
||||||
class TestAppAssertionCredentials(unittest.TestCase):
|
class TestAppAssertionCredentials(unittest2.TestCase):
|
||||||
account_name = "service_account_name@appspot.com"
|
account_name = "service_account_name@appspot.com"
|
||||||
signature = "signature"
|
signature = "signature"
|
||||||
|
|
||||||
@@ -287,20 +293,24 @@ class TestFlowModel(db.Model):
|
|||||||
flow = FlowProperty()
|
flow = FlowProperty()
|
||||||
|
|
||||||
|
|
||||||
class FlowPropertyTest(unittest.TestCase):
|
class FlowPropertyTest(unittest2.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.testbed = testbed.Testbed()
|
self.testbed = testbed.Testbed()
|
||||||
self.testbed.activate()
|
self.testbed.activate()
|
||||||
self.testbed.init_datastore_v3_stub()
|
self.testbed.init_datastore_v3_stub()
|
||||||
|
|
||||||
|
self.flow = flow_from_clientsecrets(
|
||||||
|
datafile('client_secrets.json'),
|
||||||
|
'foo',
|
||||||
|
redirect_uri='oob')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.testbed.deactivate()
|
self.testbed.deactivate()
|
||||||
|
|
||||||
def test_flow_get_put(self):
|
def test_flow_get_put(self):
|
||||||
instance = TestFlowModel(
|
instance = TestFlowModel(
|
||||||
flow=flow_from_clientsecrets(datafile('client_secrets.json'),
|
flow=self.flow,
|
||||||
'foo', redirect_uri='oob'),
|
|
||||||
key_name='foo'
|
key_name='foo'
|
||||||
)
|
)
|
||||||
instance.put()
|
instance.put()
|
||||||
@@ -308,6 +318,74 @@ class FlowPropertyTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual('foo_client_id', retrieved.flow.client_id)
|
self.assertEqual('foo_client_id', retrieved.flow.client_id)
|
||||||
|
|
||||||
|
def test_make_value_from_datastore_none(self):
|
||||||
|
self.assertIsNone(FlowProperty().make_value_from_datastore(None))
|
||||||
|
|
||||||
|
def test_validate(self):
|
||||||
|
FlowProperty().validate(None)
|
||||||
|
self.assertRaises(
|
||||||
|
db.BadValueError,
|
||||||
|
FlowProperty().validate, 42)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCredentialsModel(db.Model):
|
||||||
|
credentials = CredentialsProperty()
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialsPropertyTest(unittest2.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.testbed = testbed.Testbed()
|
||||||
|
self.testbed.activate()
|
||||||
|
self.testbed.init_datastore_v3_stub()
|
||||||
|
|
||||||
|
access_token = 'foo'
|
||||||
|
client_id = 'some_client_id'
|
||||||
|
client_secret = 'cOuDdkfjxxnv+'
|
||||||
|
refresh_token = '1/0/a.df219fjls0'
|
||||||
|
token_expiry = datetime.datetime.utcnow()
|
||||||
|
user_agent = 'refresh_checker/1.0'
|
||||||
|
self.credentials = OAuth2Credentials(
|
||||||
|
access_token, client_id, client_secret,
|
||||||
|
refresh_token, token_expiry, GOOGLE_TOKEN_URI,
|
||||||
|
user_agent)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.testbed.deactivate()
|
||||||
|
|
||||||
|
def test_credentials_get_put(self):
|
||||||
|
instance = TestCredentialsModel(
|
||||||
|
credentials=self.credentials,
|
||||||
|
key_name='foo'
|
||||||
|
)
|
||||||
|
instance.put()
|
||||||
|
retrieved = TestCredentialsModel.get_by_key_name('foo')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.credentials.to_json(),
|
||||||
|
retrieved.credentials.to_json())
|
||||||
|
|
||||||
|
def test_make_value_from_datastore(self):
|
||||||
|
self.assertIsNone(
|
||||||
|
CredentialsProperty().make_value_from_datastore(None))
|
||||||
|
self.assertIsNone(
|
||||||
|
CredentialsProperty().make_value_from_datastore(''))
|
||||||
|
self.assertIsNone(
|
||||||
|
CredentialsProperty().make_value_from_datastore('{'))
|
||||||
|
|
||||||
|
decoded = CredentialsProperty().make_value_from_datastore(
|
||||||
|
self.credentials.to_json())
|
||||||
|
self.assertEqual(
|
||||||
|
self.credentials.to_json(),
|
||||||
|
decoded.to_json())
|
||||||
|
|
||||||
|
def test_validate(self):
|
||||||
|
CredentialsProperty().validate(self.credentials)
|
||||||
|
CredentialsProperty().validate(None)
|
||||||
|
self.assertRaises(
|
||||||
|
db.BadValueError,
|
||||||
|
CredentialsProperty().validate, 42)
|
||||||
|
|
||||||
|
|
||||||
def _http_request(*args, **kwargs):
|
def _http_request(*args, **kwargs):
|
||||||
resp = httplib2.Response({'status': '200'})
|
resp = httplib2.Response({'status': '200'})
|
||||||
@@ -316,7 +394,7 @@ def _http_request(*args, **kwargs):
|
|||||||
return resp, content
|
return resp, content
|
||||||
|
|
||||||
|
|
||||||
class StorageByKeyNameTest(unittest.TestCase):
|
class StorageByKeyNameTest(unittest2.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.testbed = testbed.Testbed()
|
self.testbed = testbed.Testbed()
|
||||||
@@ -339,6 +417,27 @@ class StorageByKeyNameTest(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.testbed.deactivate()
|
self.testbed.deactivate()
|
||||||
|
|
||||||
|
def test_bad_ctor(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
StorageByKeyName(CredentialsModel, None, None)
|
||||||
|
|
||||||
|
def test__is_ndb(self):
|
||||||
|
storage = StorageByKeyName(
|
||||||
|
object(), 'foo', 'credentials')
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError, storage._is_ndb)
|
||||||
|
|
||||||
|
storage._model = type(object)
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError, storage._is_ndb)
|
||||||
|
|
||||||
|
storage._model = CredentialsModel
|
||||||
|
self.assertFalse(storage._is_ndb())
|
||||||
|
|
||||||
|
storage._model = CredentialsNDBModel
|
||||||
|
self.assertTrue(storage._is_ndb())
|
||||||
|
|
||||||
def test_get_and_put_simple(self):
|
def test_get_and_put_simple(self):
|
||||||
storage = StorageByKeyName(
|
storage = StorageByKeyName(
|
||||||
CredentialsModel, 'foo', 'credentials')
|
CredentialsModel, 'foo', 'credentials')
|
||||||
@@ -492,7 +591,7 @@ class MockRequestHandler(object):
|
|||||||
request = MockRequest()
|
request = MockRequest()
|
||||||
|
|
||||||
|
|
||||||
class DecoratorTests(unittest.TestCase):
|
class DecoratorTests(unittest2.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.testbed = testbed.Testbed()
|
self.testbed = testbed.Testbed()
|
||||||
@@ -522,7 +621,7 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
parent.had_credentials = True
|
parent.had_credentials = True
|
||||||
parent.found_credentials = decorator.credentials
|
parent.found_credentials = decorator.credentials
|
||||||
if parent.should_raise:
|
if parent.should_raise:
|
||||||
raise Exception('')
|
raise parent.should_raise
|
||||||
|
|
||||||
class TestAwareHandler(webapp2.RequestHandler):
|
class TestAwareHandler(webapp2.RequestHandler):
|
||||||
@decorator.oauth_aware
|
@decorator.oauth_aware
|
||||||
@@ -534,7 +633,7 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
parent.had_credentials = True
|
parent.had_credentials = True
|
||||||
parent.found_credentials = decorator.credentials
|
parent.found_credentials = decorator.credentials
|
||||||
if parent.should_raise:
|
if parent.should_raise:
|
||||||
raise Exception('')
|
raise parent.should_raise
|
||||||
|
|
||||||
routes = [
|
routes = [
|
||||||
('/oauth2callback', self.decorator.callback_handler()),
|
('/oauth2callback', self.decorator.callback_handler()),
|
||||||
@@ -557,6 +656,24 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
self.testbed.deactivate()
|
self.testbed.deactivate()
|
||||||
httplib2.Http = self.httplib2_orig
|
httplib2.Http = self.httplib2_orig
|
||||||
|
|
||||||
|
def test_in_error(self):
|
||||||
|
# NOTE: This branch is never reached. _in_error is not set by any code
|
||||||
|
# path. It appears to be intended to be set during construction.
|
||||||
|
self.decorator._in_error = True
|
||||||
|
self.decorator._message = 'foobar'
|
||||||
|
|
||||||
|
response = self.app.get('http://localhost/foo_path')
|
||||||
|
self.assertIn('foobar', response.body)
|
||||||
|
|
||||||
|
response = self.app.get('http://localhost/bar_path/1234/56')
|
||||||
|
self.assertIn('foobar', response.body)
|
||||||
|
|
||||||
|
def test_callback_application(self):
|
||||||
|
app = self.decorator.callback_application()
|
||||||
|
self.assertEqual(
|
||||||
|
app.router.match_routes[0].handler.__name__,
|
||||||
|
'OAuth2Handler')
|
||||||
|
|
||||||
def test_required(self):
|
def test_required(self):
|
||||||
# An initial request to an oauth_required decorated path should be a
|
# An initial request to an oauth_required decorated path should be a
|
||||||
# redirect to start the OAuth dance.
|
# redirect to start the OAuth dance.
|
||||||
@@ -610,11 +727,21 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
self.assertEqual(None, self.decorator.credentials)
|
self.assertEqual(None, self.decorator.credentials)
|
||||||
|
|
||||||
# Raising an exception still clears the Credentials.
|
# Raising an exception still clears the Credentials.
|
||||||
self.should_raise = True
|
self.should_raise = Exception('')
|
||||||
self.assertRaises(Exception, self.app.get, '/foo_path')
|
self.assertRaises(Exception, self.app.get, '/foo_path')
|
||||||
self.should_raise = False
|
self.should_raise = False
|
||||||
self.assertEqual(None, self.decorator.credentials)
|
self.assertEqual(None, self.decorator.credentials)
|
||||||
|
|
||||||
|
# Access token refresh error should start the dance again
|
||||||
|
self.should_raise = AccessTokenRefreshError()
|
||||||
|
response = self.app.get('/foo_path')
|
||||||
|
self.should_raise = False
|
||||||
|
self.assertTrue(response.status.startswith('302'))
|
||||||
|
query_params = urllib.parse.parse_qs(
|
||||||
|
response.headers['Location'].split('?', 1)[1])
|
||||||
|
self.assertEqual('http://localhost/oauth2callback',
|
||||||
|
query_params['redirect_uri'][0])
|
||||||
|
|
||||||
# Invalidate the stored Credentials.
|
# Invalidate the stored Credentials.
|
||||||
self.found_credentials.invalid = True
|
self.found_credentials.invalid = True
|
||||||
self.found_credentials.store.put(self.found_credentials)
|
self.found_credentials.store.put(self.found_credentials)
|
||||||
@@ -622,10 +749,10 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
# Invalid Credentials should start the OAuth dance again.
|
# Invalid Credentials should start the OAuth dance again.
|
||||||
response = self.app.get('/foo_path')
|
response = self.app.get('/foo_path')
|
||||||
self.assertTrue(response.status.startswith('302'))
|
self.assertTrue(response.status.startswith('302'))
|
||||||
q = urllib.parse.parse_qs(
|
query_params = urllib.parse.parse_qs(
|
||||||
response.headers['Location'].split('?', 1)[1])
|
response.headers['Location'].split('?', 1)[1])
|
||||||
self.assertEqual('http://localhost/oauth2callback',
|
self.assertEqual('http://localhost/oauth2callback',
|
||||||
q['redirect_uri'][0])
|
query_params['redirect_uri'][0])
|
||||||
|
|
||||||
def test_storage_delete(self):
|
def test_storage_delete(self):
|
||||||
# An initial request to an oauth_required decorated path should be a
|
# An initial request to an oauth_required decorated path should be a
|
||||||
@@ -710,7 +837,7 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
self.assertEqual(None, self.decorator.credentials)
|
self.assertEqual(None, self.decorator.credentials)
|
||||||
|
|
||||||
# Raising an exception still clears the Credentials.
|
# Raising an exception still clears the Credentials.
|
||||||
self.should_raise = True
|
self.should_raise = Exception('')
|
||||||
self.assertRaises(Exception, self.app.get, '/bar_path/2012/01')
|
self.assertRaises(Exception, self.app.get, '/bar_path/2012/01')
|
||||||
self.should_raise = False
|
self.should_raise = False
|
||||||
self.assertEqual(None, self.decorator.credentials)
|
self.assertEqual(None, self.decorator.credentials)
|
||||||
@@ -769,6 +896,38 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
self.assertEqual(self.decorator._revoke_uri,
|
self.assertEqual(self.decorator._revoke_uri,
|
||||||
self.decorator.credentials.revoke_uri)
|
self.decorator.credentials.revoke_uri)
|
||||||
|
|
||||||
|
def test_decorator_from_client_secrets_toplevel(self):
|
||||||
|
decorator_patch = mock.patch(
|
||||||
|
'oauth2client.contrib.appengine.OAuth2DecoratorFromClientSecrets')
|
||||||
|
|
||||||
|
with decorator_patch as decorator_mock:
|
||||||
|
filename = datafile('client_secrets.json')
|
||||||
|
decorator = oauth2decorator_from_clientsecrets(
|
||||||
|
filename,
|
||||||
|
scope='foo_scope')
|
||||||
|
decorator_mock.assert_called_once_with(
|
||||||
|
filename,
|
||||||
|
'foo_scope',
|
||||||
|
cache=None,
|
||||||
|
message=None)
|
||||||
|
|
||||||
|
def test_decorator_from_client_secrets_bad_type(self):
|
||||||
|
# NOTE: this code path is not currently reachable, as the only types
|
||||||
|
# that oauth2client.clientsecrets can load is web and installed, so
|
||||||
|
# this test forces execution of this code path. Despite not being
|
||||||
|
# normally reachable, this should remain in case future types of
|
||||||
|
# credentials are added.
|
||||||
|
|
||||||
|
loadfile_patch = mock.patch(
|
||||||
|
'oauth2client.contrib.appengine.clientsecrets.loadfile')
|
||||||
|
with loadfile_patch as loadfile_mock:
|
||||||
|
loadfile_mock.return_value = ('badtype', None)
|
||||||
|
self.assertRaises(
|
||||||
|
AppEngineInvalidClientSecretsError,
|
||||||
|
OAuth2DecoratorFromClientSecrets,
|
||||||
|
'doesntmatter.json',
|
||||||
|
scope=['foo_scope', 'bar_scope'])
|
||||||
|
|
||||||
def test_decorator_from_client_secrets_kwargs(self):
|
def test_decorator_from_client_secrets_kwargs(self):
|
||||||
decorator = OAuth2DecoratorFromClientSecrets(
|
decorator = OAuth2DecoratorFromClientSecrets(
|
||||||
datafile('client_secrets.json'),
|
datafile('client_secrets.json'),
|
||||||
@@ -830,8 +989,31 @@ class DecoratorTests(unittest.TestCase):
|
|||||||
except InvalidClientSecretsError:
|
except InvalidClientSecretsError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_decorator_from_client_secrets_with_optional_settings(self):
|
||||||
|
# Test that the decorator works with the absense of a revoke_uri in
|
||||||
|
# the client secrets.
|
||||||
|
loadfile_patch = mock.patch(
|
||||||
|
'oauth2client.contrib.appengine.clientsecrets.loadfile')
|
||||||
|
with loadfile_patch as loadfile_mock:
|
||||||
|
loadfile_mock.return_value = (TYPE_WEB, {
|
||||||
|
"client_id": "foo_client_id",
|
||||||
|
"client_secret": "foo_client_secret",
|
||||||
|
"redirect_uris": [],
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||||
|
"token_uri": "https://www.googleapis.com/oauth2/v4/token",
|
||||||
|
# No revoke URI
|
||||||
|
})
|
||||||
|
|
||||||
class DecoratorXsrfSecretTests(unittest.TestCase):
|
decorator = OAuth2DecoratorFromClientSecrets(
|
||||||
|
'doesntmatter.json',
|
||||||
|
scope=['foo_scope', 'bar_scope'])
|
||||||
|
|
||||||
|
self.assertEqual(decorator._revoke_uri, GOOGLE_REVOKE_URI)
|
||||||
|
# This is never set, but it's consistent with other tests.
|
||||||
|
self.assertFalse(decorator._in_error)
|
||||||
|
|
||||||
|
|
||||||
|
class DecoratorXsrfSecretTests(unittest2.TestCase):
|
||||||
"""Test xsrf_secret_key."""
|
"""Test xsrf_secret_key."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -880,7 +1062,7 @@ class DecoratorXsrfSecretTests(unittest.TestCase):
|
|||||||
self.assertEqual(site_key.secret, secret)
|
self.assertEqual(site_key.secret, secret)
|
||||||
|
|
||||||
|
|
||||||
class DecoratorXsrfProtectionTests(unittest.TestCase):
|
class DecoratorXsrfProtectionTests(unittest2.TestCase):
|
||||||
"""Test _build_state_value and _parse_state_value."""
|
"""Test _build_state_value and _parse_state_value."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -902,4 +1084,4 @@ class DecoratorXsrfProtectionTests(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__': # pragma: NO COVER
|
if __name__ == '__main__': # pragma: NO COVER
|
||||||
unittest.main()
|
unittest2.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user