diff --git a/tests/contrib/test_appengine.py b/tests/contrib/test_appengine.py index cdaf6c5..7e6050e 100644 --- a/tests/contrib/test_appengine.py +++ b/tests/contrib/test_appengine.py @@ -31,7 +31,6 @@ from google.appengine.api.memcache import memcache_stub from google.appengine.ext import db from google.appengine.ext import ndb from google.appengine.ext import testbed -import httplib2 import mock from six.moves import urllib import unittest2 @@ -42,11 +41,19 @@ import oauth2client from oauth2client import client from oauth2client import clientsecrets from oauth2client.contrib import appengine -from ..http_mock import CacheMock +from .. import http_mock __author__ = 'jcgregorio@google.com (Joe Gregorio)' DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DEFAULT_RESP = """\ +{ + "access_token": "foo_access_token", + "expires_in": 3600, + "extra": "value", + "refresh_token": "foo_refresh_token" +} +""" def datafile(filename): @@ -75,22 +82,6 @@ class UserNotLoggedInMock(object): return None -class Http2Mock(object): - """Mock httplib2.Http""" - status = 200 - content = { - 'access_token': 'foo_access_token', - 'refresh_token': 'foo_refresh_token', - 'expires_in': 3600, - 'extra': 'value', - } - - def request(self, token_uri, method, body, headers, *args, **kwargs): - self.body = body - self.headers = headers - return self, json.dumps(self.content) - - class TestAppAssertionCredentials(unittest2.TestCase): account_name = "service_account_name@appspot.com" signature = "signature" @@ -139,7 +130,7 @@ class TestAppAssertionCredentials(unittest2.TestCase): scope = 'http://www.googleapis.com/scope' credentials = appengine.AppAssertionCredentials(scope) - http = httplib2.Http() + http = http_mock.HttpMock(data=DEFAULT_RESP) with self.assertRaises(client.AccessTokenRefreshError): credentials.refresh(http) @@ -155,7 +146,7 @@ class TestAppAssertionCredentials(unittest2.TestCase): "http://www.googleapis.com/scope", "http://www.googleapis.com/scope2"] credentials = appengine.AppAssertionCredentials(scope) - http = httplib2.Http() + http = http_mock.HttpMock(data=DEFAULT_RESP) credentials.refresh(http) self.assertEqual('a_token_123', credentials.access_token) @@ -168,7 +159,7 @@ class TestAppAssertionCredentials(unittest2.TestCase): scope = ('http://www.googleapis.com/scope ' 'http://www.googleapis.com/scope2') credentials = appengine.AppAssertionCredentials(scope) - http = httplib2.Http() + http = http_mock.HttpMock(data=DEFAULT_RESP) credentials.refresh(http) self.assertEqual('a_token_123', credentials.access_token) self.assertEqual( @@ -184,7 +175,7 @@ class TestAppAssertionCredentials(unittest2.TestCase): autospec=True) as get_access_token: credentials = appengine.AppAssertionCredentials( scope, service_account_id=account_id) - http = httplib2.Http() + http = http_mock.HttpMock(data=DEFAULT_RESP) credentials.refresh(http) self.assertEqual('a_token_456', credentials.access_token) @@ -370,7 +361,7 @@ class CredentialsPropertyTest(unittest2.TestCase): def _http_request(*args, **kwargs): - resp = httplib2.Response({'status': '200'}) + resp = http_mock.ResponseMock() content = json.dumps({'access_token': 'bar'}) return resp, content @@ -630,12 +621,9 @@ class DecoratorTests(unittest2.TestCase): }) self.current_user = user_mock() users.get_current_user = self.current_user - self.httplib2_orig = httplib2.Http - httplib2.Http = Http2Mock def tearDown(self): self.testbed.deactivate() - httplib2.Http = self.httplib2_orig def test_in_error(self): # NOTE: This branch is never reached. _in_error is not set by any code @@ -655,7 +643,9 @@ class DecoratorTests(unittest2.TestCase): app.router.match_routes[0].handler.__name__, 'OAuth2Handler') - def test_required(self): + @mock.patch('oauth2client.transport.get_http_object') + def test_required(self, new_http): + new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP) # An initial request to an oauth_required decorated path should be a # redirect to start the OAuth dance. self.assertEqual(self.decorator.flow, None) @@ -688,7 +678,7 @@ class DecoratorTests(unittest2.TestCase): response_query = urllib.parse.parse_qs(parts[1]) response = response_query[ self.decorator._token_response_param][0] - self.assertEqual(Http2Mock.content, + self.assertEqual(json.loads(DEFAULT_RESP), json.loads(urllib.parse.unquote(response))) self.assertEqual(self.decorator.flow, self.decorator._tls.flow) self.assertEqual(self.decorator.credentials, @@ -736,7 +726,12 @@ class DecoratorTests(unittest2.TestCase): self.assertEqual('http://localhost/oauth2callback', query_params['redirect_uri'][0]) - def test_storage_delete(self): + # Check the mocks were called. + new_http.assert_called_once_with() + + @mock.patch('oauth2client.transport.get_http_object') + def test_storage_delete(self, new_http): + new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP) # An initial request to an oauth_required decorated path should be a # redirect to start the OAuth dance. response = self.app.get('/foo_path') @@ -772,7 +767,12 @@ class DecoratorTests(unittest2.TestCase): parse_state_value.assert_called_once_with( 'foo_path:xsrfkey123', self.current_user) - def test_aware(self): + # Check the mocks were called. + new_http.assert_called_once_with() + + @mock.patch('oauth2client.transport.get_http_object') + def test_aware(self, new_http): + new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP) # An initial request to an oauth_aware decorated path should # not redirect. response = self.app.get('http://localhost/bar_path/2012/01') @@ -825,6 +825,9 @@ class DecoratorTests(unittest2.TestCase): self.should_raise = False self.assertEqual(None, self.decorator.credentials) + # Check the mocks were called. + new_http.assert_called_once_with() + def test_error_in_step2(self): # An initial request to an oauth_aware decorated path should # not redirect. @@ -855,10 +858,14 @@ class DecoratorTests(unittest2.TestCase): self.assertEqual(decorator.flow, decorator._tls.flow) def test_token_response_param(self): + # No need to set-up a mock since test_required() does. self.decorator._token_response_param = 'foobar' self.test_required() - def test_decorator_from_client_secrets(self): + @mock.patch('oauth2client.transport.get_http_object') + def test_decorator_from_client_secrets(self, new_http): + new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP) + # Execute test after setting up mock. decorator = appengine.OAuth2DecoratorFromClientSecrets( datafile('client_secrets.json'), scope=['foo_scope', 'bar_scope']) @@ -877,6 +884,9 @@ class DecoratorTests(unittest2.TestCase): self.assertEqual(self.decorator._revoke_uri, self.decorator.credentials.revoke_uri) + # Check the mocks were called. + new_http.assert_called_once_with() + def test_decorator_from_client_secrets_toplevel(self): decorator_patch = mock.patch( 'oauth2client.contrib.appengine.OAuth2DecoratorFromClientSecrets') @@ -915,7 +925,7 @@ class DecoratorTests(unittest2.TestCase): self.assertIn('prompt', decorator._kwargs) def test_decorator_from_cached_client_secrets(self): - cache_mock = CacheMock() + cache_mock = http_mock.CacheMock() load_and_cache('client_secrets.json', 'secret', cache_mock) decorator = appengine.OAuth2DecoratorFromClientSecrets( # filename, scope, message=None, cache=None @@ -991,7 +1001,10 @@ class DecoratorTests(unittest2.TestCase): # This is never set, but it's consistent with other tests. self.assertFalse(decorator._in_error) - def test_invalid_state(self): + @mock.patch('oauth2client.transport.get_http_object') + def test_invalid_state(self, new_http): + new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP) + # Execute test after setting up mock. with mock.patch.object(appengine, '_parse_state_value', return_value=None, autospec=True): # Now simulate the callback to /oauth2callback. @@ -1002,6 +1015,9 @@ class DecoratorTests(unittest2.TestCase): self.assertEqual('200 OK', response.status) self.assertEqual('The authorization request failed', response.body) + # Check the mocks were called. + new_http.assert_called_once_with() + class DecoratorXsrfSecretTests(unittest2.TestCase): """Test xsrf_secret_key.""" diff --git a/tests/contrib/test_flask_util.py b/tests/contrib/test_flask_util.py index 74cb218..b80e08c 100644 --- a/tests/contrib/test_flask_util.py +++ b/tests/contrib/test_flask_util.py @@ -19,7 +19,6 @@ import json import logging import flask -import httplib2 import mock import six.moves.http_client as httplib import six.moves.urllib.parse as urlparse @@ -29,39 +28,20 @@ import oauth2client from oauth2client import client from oauth2client import clientsecrets from oauth2client.contrib import flask_util +from .. import http_mock __author__ = 'jonwayne@google.com (Jon Wayne Parrott)' -class Http2Mock(object): - """Mock httplib2.Http for code exchange / refresh""" - - def __init__(self, status=httplib.OK, **kwargs): - self.status = status - self.content = { - 'access_token': 'foo_access_token', - 'refresh_token': 'foo_refresh_token', - 'expires_in': 3600, - 'extra': 'value', - } - self.content.update(kwargs) - - def request(self, token_uri, method, body, headers, *args, **kwargs): - self.body = body - self.headers = headers - return (self, json.dumps(self.content).encode('utf-8')) - - def __enter__(self): - self.httplib2_orig = httplib2.Http - httplib2.Http = self - return self - - def __exit__(self, exc_type, exc_value, traceback): - httplib2.Http = self.httplib2_orig - - def __call__(self, *args, **kwargs): - return self +DEFAULT_RESP = """\ +{ + "access_token": "foo_access_token", + "expires_in": 3600, + "extra": "value", + "refresh_token": "foo_refresh_token" +} +""" class FlaskOAuth2Tests(unittest2.TestCase): @@ -246,7 +226,12 @@ class FlaskOAuth2Tests(unittest2.TestCase): def test_callback_view(self): self.oauth2.storage = mock.Mock() with self.app.test_client() as client: - with Http2Mock() as http: + with mock.patch( + 'oauth2client.transport.get_http_object') as new_http: + # Set-up mock. + new_http.return_value = http = http_mock.HttpMock( + data=DEFAULT_RESP) + # Run tests. state = self._setup_callback_state(client) response = client.get( @@ -258,6 +243,9 @@ class FlaskOAuth2Tests(unittest2.TestCase): self.assertIn('codez', http.body) self.assertTrue(self.oauth2.storage.put.called) + # Check the mocks were called. + new_http.assert_called_once_with() + def test_authorize_callback(self): self.oauth2.authorize_callback = mock.Mock() self.test_callback_view() @@ -296,11 +284,20 @@ class FlaskOAuth2Tests(unittest2.TestCase): with self.app.test_client() as client: state = self._setup_callback_state(client) - with Http2Mock(status=httplib.INTERNAL_SERVER_ERROR): + with mock.patch( + 'oauth2client.transport.get_http_object') as new_http: + # Set-up mock. + new_http.return_value = http_mock.HttpMock( + headers={'status': httplib.INTERNAL_SERVER_ERROR}, + data=DEFAULT_RESP) + # Run tests. response = client.get( '/oauth2callback?state={0}&code=codez'.format(state)) self.assertEqual(response.status_code, httplib.BAD_REQUEST) + # Check the mocks were called. + new_http.assert_called_once_with() + # Invalid state json with self.app.test_client() as client: with client.session_transaction() as session: @@ -495,7 +492,10 @@ class FlaskOAuth2Tests(unittest2.TestCase): def test_incremental_auth_exchange(self): self._create_incremental_auth_app() - with Http2Mock(): + with mock.patch('oauth2client.transport.get_http_object') as new_http: + # Set-up mock. + new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP) + # Run tests. with self.app.test_client() as client: state = self._setup_callback_state( client, @@ -511,16 +511,21 @@ class FlaskOAuth2Tests(unittest2.TestCase): self.assertTrue( credentials.has_scopes(['email', 'one', 'two'])) + # Check the mocks were called. + new_http.assert_called_once_with() + def test_refresh(self): + token_val = 'new_token' + json_resp = '{"access_token": "%s"}' % (token_val,) + http = http_mock.HttpMock(data=json_resp) with self.app.test_request_context(): with mock.patch('flask.session'): self.oauth2.storage.put(self._generate_credentials()) - self.oauth2.credentials.refresh( - Http2Mock(access_token='new_token')) + self.oauth2.credentials.refresh(http) self.assertEqual( - self.oauth2.storage.get().access_token, 'new_token') + self.oauth2.storage.get().access_token, token_val) def test_delete(self): with self.app.test_request_context(): diff --git a/tests/contrib/test_gce.py b/tests/contrib/test_gce.py index e71bd44..d71a391 100644 --- a/tests/contrib/test_gce.py +++ b/tests/contrib/test_gce.py @@ -17,7 +17,6 @@ import datetime import json -import httplib2 import mock from six.moves import http_client from tests.contrib.test_metadata import request_mock @@ -129,12 +128,12 @@ class AppAssertionCredentialsTests(unittest2.TestCase): service_account='default') @mock.patch('oauth2client.contrib._metadata.get_service_account_info', - side_effect=httplib2.HttpLib2Error('No Such Email')) + side_effect=http_client.HTTPException('No Such Email')) def test_retrieve_scopes_bad_email(self, metadata): http_request = mock.MagicMock() http_mock = mock.MagicMock(request=http_request) credentials = gce.AppAssertionCredentials(email='b@example.com') - with self.assertRaises(httplib2.HttpLib2Error): + with self.assertRaises(http_client.HTTPException): credentials.retrieve_scopes(http_mock) metadata.assert_called_once_with(http_request, diff --git a/tests/contrib/test_metadata.py b/tests/contrib/test_metadata.py index 39d7291..a8544e3 100644 --- a/tests/contrib/test_metadata.py +++ b/tests/contrib/test_metadata.py @@ -15,12 +15,13 @@ import datetime import json -import httplib2 import mock from six.moves import http_client import unittest2 from oauth2client.contrib import _metadata +from .. import http_mock + PATH = 'instance/service-accounts/default' DATA = {'foo': 'bar'} @@ -31,12 +32,9 @@ EXPECTED_KWARGS = dict(headers=_metadata.METADATA_HEADERS) def request_mock(status, content_type, content): - return mock.MagicMock(return_value=( - httplib2.Response( - {'status': status, 'content-type': content_type} - ), - content.encode('utf-8') - )) + resp = http_mock.ResponseMock( + {'status': status, 'content-type': content_type}) + return mock.Mock(return_value=(resp, content.encode('utf-8'))) class TestMetadata(unittest2.TestCase): diff --git a/tests/http_mock.py b/tests/http_mock.py index 6053299..34dc5e5 100644 --- a/tests/http_mock.py +++ b/tests/http_mock.py @@ -12,17 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Copy of googleapiclient.http's mock functionality.""" +"""HTTP helpers mock functionality.""" -import httplib2 -# TODO(craigcitro): Find a cleaner way to share this code with googleapiclient. +class ResponseMock(dict): + """Mock HTTP response""" + + def __init__(self, vals=None): + if vals is None: + vals = {} + self.update(vals) + self.status = int(self.get('status', 200)) class HttpMock(object): - """Mock of httplib2.Http""" + """Mock of HTTP object.""" - def __init__(self, headers=None): + def __init__(self, headers=None, data=None): """HttpMock constructor. Args: @@ -30,7 +36,7 @@ class HttpMock(object): """ if headers is None: headers = {'status': '200'} - self.data = None + self.data = data self.response_headers = headers self.headers = None self.uri = None @@ -48,15 +54,15 @@ class HttpMock(object): self.method = method self.body = body self.headers = headers - return httplib2.Response(self.response_headers), self.data + return ResponseMock(self.response_headers), self.data class HttpMockSequence(object): - """Mock of httplib2.Http + """Mock of HTTP object with multiple return values. Mocks a sequence of calls to request returning different responses for each call. Create an instance initialized with the desired response headers - and content and then use as if an httplib2.Http instance:: + and content and then use as if an HttpMock instance:: http = HttpMockSequence([ ({'status': '401'}, b''), @@ -99,7 +105,7 @@ class HttpMockSequence(object): elif content == 'echo_request_body': content = (body if body_stream_content is None else body_stream_content) - return httplib2.Response(resp), content + return ResponseMock(resp), content class CacheMock(object): diff --git a/tests/test_client.py b/tests/test_client.py index db75603..64a7d7b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -27,7 +27,6 @@ import socket import sys import tempfile -import httplib2 import mock import six from six.moves import http_client @@ -40,9 +39,7 @@ from oauth2client import client from oauth2client import clientsecrets from oauth2client import service_account from oauth2client import util -from .http_mock import CacheMock -from .http_mock import HttpMock -from .http_mock import HttpMockSequence +from . import http_mock __author__ = 'jcgregorio@google.com (Joe Gregorio)' @@ -855,7 +852,7 @@ def _token_revoke_test_helper(testcase, status, revoke_raise, return actual_do_revoke(http_request, token) testcase.credentials._do_revoke = do_revoke_stub - http = HttpMock(headers={'status': status}) + http = http_mock.HttpMock(headers={'status': status}) if revoke_raise: testcase.assertRaises(client.TokenRevokeError, testcase.credentials.revoke, http) @@ -898,11 +895,11 @@ class BasicCredentialsTests(unittest2.TestCase): def test_token_refresh_success(self): for status_code in client.REFRESH_STATUS_CODES: token_response = {'access_token': '1/3w', 'expires_in': 3600} - http = HttpMockSequence([ + json_resp = json.dumps(token_response).encode('utf-8') + http = http_mock.HttpMockSequence([ ({'status': status_code}, b''), - ({'status': '200'}, json.dumps(token_response).encode( - 'utf-8')), - ({'status': '200'}, 'echo_request_headers'), + ({'status': http_client.OK}, json_resp), + ({'status': http_client.OK}, 'echo_request_headers'), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -911,28 +908,24 @@ class BasicCredentialsTests(unittest2.TestCase): self.assertEqual(token_response, self.credentials.token_response) def test_recursive_authorize(self): - """Tests that OAuth2Credentials doesn't intro. new method constraints. - - Formerly, OAuth2Credentials.authorize monkeypatched the request method - of its httplib2.Http argument with a wrapper annotated with - @util.positional(1). Since the original method has no such annotation, - that meant that the wrapper was violating the contract of the original - method by adding a new requirement to it. And in fact the wrapper - itself doesn't even respect that requirement. So before the removal of - the annotation, this test would fail. - """ + # Tests that OAuth2Credentials doesn't intro. new method constraints. + # Formerly, OAuth2Credentials.authorize monkeypatched the request method + # of the passed in HTTP object with a wrapper annotated with + # @util.positional(1). Since the original method has no such annotation, + # that meant that the wrapper was violating the contract of the original + # method by adding a new requirement to it. And in fact the wrapper + # itself doesn't even respect that requirement. So before the removal of + # the annotation, this test would fail. token_response = {'access_token': '1/3w', 'expires_in': 3600} encoded_response = json.dumps(token_response).encode('utf-8') - http = HttpMockSequence([ - ({'status': '200'}, encoded_response), - ]) + http = http_mock.HttpMock(data=encoded_response) http = self.credentials.authorize(http) http = self.credentials.authorize(http) http.request('http://example.com') def test_token_refresh_failure(self): for status_code in client.REFRESH_STATUS_CODES: - http = HttpMockSequence([ + http = http_mock.HttpMockSequence([ ({'status': status_code}, b''), ({'status': http_client.BAD_REQUEST}, b'{"error":"access_denied"}'), @@ -965,9 +958,7 @@ class BasicCredentialsTests(unittest2.TestCase): self.credentials = self.credentials.from_json(original_credentials) def test_non_401_error_response(self): - http = HttpMockSequence([ - ({'status': '400'}, b''), - ]) + http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST}) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') self.assertEqual(http_client.BAD_REQUEST, resp.status) @@ -1010,7 +1001,7 @@ class BasicCredentialsTests(unittest2.TestCase): # First, test that we correctly encode basic objects, making sure # to include a bytes object. Note that oauth2client will normalize # everything to bytes, no matter what python version we're in. - http = credentials.authorize(HttpMock()) + http = credentials.authorize(http_mock.HttpMock()) headers = {u'foo': 3, b'bar': True, 'baz': b'abc'} cleaned_headers = {b'foo': b'3', b'bar': b'True', b'baz': b'abc'} http.request(u'http://example.com', method=u'GET', headers=headers) @@ -1037,7 +1028,7 @@ class BasicCredentialsTests(unittest2.TestCase): access_token, client_id, client_secret, refresh_token, token_expiry, token_uri, user_agent, revoke_uri=revoke_uri) - http = HttpMock() + http = http_mock.HttpMock() http = credentials.authorize(http) http.request(u'http://example.com', method=u'GET', headers={u'foo': u'bar'}) @@ -1107,7 +1098,7 @@ class BasicCredentialsTests(unittest2.TestCase): 'access_token': token2, 'expires_in': lifetime, } - http = HttpMockSequence([ + http = http_mock.HttpMockSequence([ ({'status': '200'}, json.dumps(token_response_first).encode( 'utf-8')), ({'status': '200'}, json.dumps(token_response_second).encode( @@ -1181,11 +1172,12 @@ class BasicCredentialsTests(unittest2.TestCase): # Specify a token so we can use it in the response. credentials.access_token = 'ya29-s3kr3t' - with mock.patch('httplib2.Http', - return_value=object) as http_kls: + with mock.patch('oauth2client.transport.get_http_object', + return_value=object()) as new_http: token_info = credentials.get_access_token() expires_in.assert_called_once_with() - refresh_mock.assert_called_once_with(http_kls.return_value) + refresh_mock.assert_called_once_with(new_http.return_value) + new_http.assert_called_once_with() self.assertIsInstance(token_info, client.AccessTokenInfo) self.assertEqual(token_info.access_token, @@ -1249,33 +1241,26 @@ class BasicCredentialsTests(unittest2.TestCase): store.locked_put.assert_called_once_with(credentials) def test__do_refresh_request_non_json_failure(self): - response = httplib2.Response({ - 'status': int(http_client.BAD_REQUEST), - }) + response = http_mock.ResponseMock({'status': http_client.BAD_REQUEST}) content = u'Bad request' error_msg = 'Invalid response {0}.'.format(int(response.status)) self._do_refresh_request_test_helper(response, content, error_msg) def test__do_refresh_request_basic_failure(self): - response = httplib2.Response({ - 'status': int(http_client.INTERNAL_SERVER_ERROR), - }) + response = http_mock.ResponseMock( + {'status': http_client.INTERNAL_SERVER_ERROR}) content = u'{}' error_msg = 'Invalid response {0}.'.format(int(response.status)) self._do_refresh_request_test_helper(response, content, error_msg) def test__do_refresh_request_failure_w_json_error(self): - response = httplib2.Response({ - 'status': http_client.BAD_GATEWAY, - }) + response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY}) error_msg = 'Hi I am an error not a bearer' content = json.dumps({'error': error_msg}) self._do_refresh_request_test_helper(response, content, error_msg) def test__do_refresh_request_failure_w_json_error_and_store(self): - response = httplib2.Response({ - 'status': http_client.BAD_GATEWAY, - }) + response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY}) error_msg = 'Where are we going wearer?' content = json.dumps({'error': error_msg}) store = mock.MagicMock() @@ -1283,9 +1268,8 @@ class BasicCredentialsTests(unittest2.TestCase): store=store) def test__do_refresh_request_failure_w_json_error_and_desc(self): - response = httplib2.Response({ - 'status': http_client.SERVICE_UNAVAILABLE, - }) + response = http_mock.ResponseMock( + {'status': http_client.SERVICE_UNAVAILABLE}) base_error = 'Ruckus' error_desc = 'Can you describe the ruckus' content = json.dumps({ @@ -1328,46 +1312,35 @@ class BasicCredentialsTests(unittest2.TestCase): logger.info.assert_called_once_with('Revoking token') def test__do_revoke_success(self): - response = httplib2.Response({ - 'status': http_client.OK, - }) + response = http_mock.ResponseMock() self._do_revoke_test_helper(response, b'', None) def test__do_revoke_success_with_store(self): - response = httplib2.Response({ - 'status': http_client.OK, - }) + response = http_mock.ResponseMock() store = mock.MagicMock() self._do_revoke_test_helper(response, b'', None, store=store) def test__do_revoke_non_json_failure(self): - response = httplib2.Response({ - 'status': http_client.BAD_REQUEST, - }) + response = http_mock.ResponseMock({'status': http_client.BAD_REQUEST}) content = u'Bad request' error_msg = 'Invalid response {0}.'.format(response.status) self._do_revoke_test_helper(response, content, error_msg) def test__do_revoke_basic_failure(self): - response = httplib2.Response({ - 'status': http_client.INTERNAL_SERVER_ERROR, - }) + response = http_mock.ResponseMock( + {'status': http_client.INTERNAL_SERVER_ERROR}) content = u'{}' error_msg = 'Invalid response {0}.'.format(response.status) self._do_revoke_test_helper(response, content, error_msg) def test__do_revoke_failure_w_json_error(self): - response = httplib2.Response({ - 'status': http_client.BAD_GATEWAY, - }) + response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY}) error_msg = 'Hi I am an error not a bearer' content = json.dumps({'error': error_msg}) self._do_revoke_test_helper(response, content, error_msg) def test__do_revoke_failure_w_json_error_and_store(self): - response = httplib2.Response({ - 'status': http_client.BAD_GATEWAY, - }) + response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY}) error_msg = 'Where are we going wearer?' content = json.dumps({'error': error_msg}) store = mock.MagicMock() @@ -1409,41 +1382,32 @@ class BasicCredentialsTests(unittest2.TestCase): logger.info.assert_called_once_with('Refreshing scopes') def test__do_retrieve_scopes_success_bad_json(self): - response = httplib2.Response({ - 'status': http_client.OK, - }) + response = http_mock.ResponseMock() invalid_json = b'{' with self.assertRaises(ValueError): self._do_retrieve_scopes_test_helper(response, invalid_json, None) def test__do_retrieve_scopes_success(self): - response = httplib2.Response({ - 'status': http_client.OK, - }) + response = http_mock.ResponseMock() content = b'{"scope": "foo bar"}' self._do_retrieve_scopes_test_helper(response, content, None, scopes=set(['foo', 'bar'])) def test__do_retrieve_scopes_non_json_failure(self): - response = httplib2.Response({ - 'status': http_client.BAD_REQUEST, - }) + response = http_mock.ResponseMock({'status': http_client.BAD_REQUEST}) content = u'Bad request' error_msg = 'Invalid response {0}.'.format(response.status) self._do_retrieve_scopes_test_helper(response, content, error_msg) def test__do_retrieve_scopes_basic_failure(self): - response = httplib2.Response({ - 'status': http_client.INTERNAL_SERVER_ERROR, - }) + response = http_mock.ResponseMock( + {'status': http_client.INTERNAL_SERVER_ERROR}) content = u'{}' error_msg = 'Invalid response {0}.'.format(response.status) self._do_retrieve_scopes_test_helper(response, content, error_msg) def test__do_retrieve_scopes_failure_w_json_error(self): - response = httplib2.Response({ - 'status': http_client.BAD_GATEWAY, - }) + response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY}) error_msg = 'Error desc I sit at a desk' content = json.dumps({'error_description': error_msg}) self._do_retrieve_scopes_test_helper(response, content, error_msg) @@ -1467,7 +1431,7 @@ class BasicCredentialsTests(unittest2.TestCase): def test_retrieve_scopes(self): info_response_first = {'scope': 'foo bar'} info_response_second = {'error_description': 'abcdef'} - http = HttpMockSequence([ + http = http_mock.HttpMockSequence([ ({'status': '200'}, json.dumps(info_response_first).encode( 'utf-8')), ({'status': '400'}, json.dumps(info_response_second).encode( @@ -1496,7 +1460,7 @@ class BasicCredentialsTests(unittest2.TestCase): b' "expires_in":3600,' b' "id_token": "' + jwt + b'"' b'}') - http = HttpMockSequence([ + http = http_mock.HttpMockSequence([ ({'status': status_code}, b''), ({'status': '200'}, token_response), ({'status': '200'}, 'echo_request_headers'), @@ -1517,9 +1481,8 @@ class AccessTokenCredentialsTests(unittest2.TestCase): def test_token_refresh_success(self): for status_code in client.REFRESH_STATUS_CODES: - http = HttpMockSequence([ - ({'status': status_code}, b''), - ]) + http = http_mock.HttpMock( + headers={'status': status_code}, data=b'') http = self.credentials.authorize(http) with self.assertRaises(client.AccessTokenCredentialsError): resp, content = http.request('http://example.com') @@ -1535,15 +1498,13 @@ class AccessTokenCredentialsTests(unittest2.TestCase): valid_bool_value=False, token_attr='access_token') def test_non_401_error_response(self): - http = HttpMockSequence([ - ({'status': '400'}, b''), - ]) + http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST}) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') self.assertEqual(http_client.BAD_REQUEST, resp.status) def test_auth_header_sent(self): - http = HttpMockSequence([ + http = http_mock.HttpMockSequence([ ({'status': '200'}, 'echo_request_headers'), ]) http = self.credentials.authorize(http) @@ -1578,7 +1539,7 @@ class TestAssertionCredentials(unittest2.TestCase): body['grant_type'][0]) def test_assertion_refresh(self): - http = HttpMockSequence([ + http = http_mock.HttpMockSequence([ ({'status': '200'}, b'{"access_token":"1/3w"}'), ({'status': '200'}, 'echo_request_headers'), ]) @@ -1758,12 +1719,15 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): 'user_code': user_code, 'verification_url': ver_url, }) - http = HttpMockSequence([ + http = http_mock.HttpMockSequence([ ({'status': http_client.OK}, content), ]) if default_http: - with mock.patch('httplib2.Http', return_value=http): + with mock.patch('oauth2client.transport.get_http_object', + return_value=http) as new_http: result = flow.step1_get_device_and_user_codes() + # Check the mock was called. + new_http.assert_called_once_with() else: result = flow.step1_get_device_and_user_codes(http=http) @@ -1803,9 +1767,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): def _step1_get_device_and_user_codes_fail_helper(self, status, content, error_msg): flow = client.OAuth2WebServerFlow('CID', scope='foo') - http = HttpMockSequence([ - ({'status': status}, content), - ]) + http = http_mock.HttpMock(headers={'status': status}, data=content) with self.assertRaises(client.OAuth2DeviceCodeError) as exc_manager: flow.step1_get_device_and_user_codes(http=http) @@ -1849,17 +1811,19 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): client.OAuth2WebServerFlow('client_id+1') def test_exchange_failure(self): - http = HttpMockSequence([ - ({'status': '400'}, b'{"error":"invalid_request"}'), - ]) + http = http_mock.HttpMock( + headers={'status': http_client.BAD_REQUEST}, + data=b'{"error":"invalid_request"}', + ) with self.assertRaises(client.FlowExchangeError): self.flow.step2_exchange(code='some random code', http=http) def test_urlencoded_exchange_failure(self): - http = HttpMockSequence([ - ({'status': '400'}, b'error=invalid_request'), - ]) + http = http_mock.HttpMock( + headers={'status': http_client.BAD_REQUEST}, + data=b'error=invalid_request', + ) with self.assertRaisesRegexp(client.FlowExchangeError, 'invalid_request'): @@ -1876,7 +1840,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): b' "type": "OAuthException"' b' }' b'}') - http = HttpMockSequence([({'status': '400'}, payload)]) + http = http_mock.HttpMock(data=payload) with self.assertRaises(client.FlowExchangeError): self.flow.step2_exchange(code='some random code', http=http) @@ -1887,7 +1851,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): b' "expires_in":3600,' b' "refresh_token":"8xLOxBtZp8"' b'}') - http = HttpMockSequence([({'status': '200'}, payload)]) + http = http_mock.HttpMock(data=payload) credentials = self.flow.step2_exchange( code=code, device_flow_info=device_flow_info, http=http) self.assertEqual('SlAV32hkKG', credentials.access_token) @@ -1916,8 +1880,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): ' "expires_in":' + expires_in + ',' ' "refresh_token":"' + refresh_token + '"' '}') - http = HttpMockSequence( - [({'status': '200'}, _helpers._to_bytes(payload))]) + http = http_mock.HttpMock(data=_helpers._to_bytes(payload)) credentials = self.flow.step2_exchange(code=binary_code, http=http) self.assertEqual(access_token, credentials.access_token) self.assertIsNotNone(credentials.token_expiry) @@ -1943,7 +1906,9 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): b' "expires_in":3600,' b' "refresh_token":"8xLOxBtZp8"' b'}') - http = HttpMockSequence([({'status': '200'}, payload)]) + http = http_mock.HttpMockSequence([ + ({'status': http_client.OK}, payload), + ]) credentials = self.flow.step2_exchange(code=not_a_dict, http=http) self.assertEqual('SlAV32hkKG', credentials.access_token) @@ -1951,6 +1916,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): self.assertEqual('8xLOxBtZp8', credentials.refresh_token) self.assertEqual('dummy_revoke_uri', credentials.revoke_uri) self.assertEqual(set(['foo']), credentials.scopes) + self.assertEqual(len(http.requests), 1) request_code = urllib.parse.parse_qs( http.requests[0]['body'])['code'][0] self.assertEqual(code, request_code) @@ -1965,13 +1931,14 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): user_agent='unittest-sample/1.0', revoke_uri='dummy_revoke_uri', ) - http = HttpMockSequence([ - ({'status': '200'}, b'access_token=SlAV32hkKG'), + http = http_mock.HttpMockSequence([ + ({'status': http_client.OK}, b'access_token=SlAV32hkKG'), ]) credentials = flow.step2_exchange(code='some random code', http=http) self.assertEqual('SlAV32hkKG', credentials.access_token) + self.assertEqual(len(http.requests), 1) test_request = http.requests[0] # Did we pass the Authorization header? self.assertEqual(test_request['headers']['Authorization'], auth_header) @@ -1979,9 +1946,8 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): self.assertTrue('client_secret' not in test_request['body']) def test_urlencoded_exchange_success(self): - http = HttpMockSequence([ - ({'status': '200'}, b'access_token=SlAV32hkKG&expires_in=3600'), - ]) + http = http_mock.HttpMock( + data=b'access_token=SlAV32hkKG&expires_in=3600') credentials = self.flow.step2_exchange(code='some random code', http=http) @@ -1989,12 +1955,9 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): self.assertNotEqual(None, credentials.token_expiry) def test_urlencoded_expires_param(self): - http = HttpMockSequence([ - # Note the 'expires=3600' where you'd normally - # have if named 'expires_in' - ({'status': '200'}, b'access_token=SlAV32hkKG&expires=3600'), - ]) - + # Note the 'expires=3600' where you'd normally + # have if named 'expires_in' + http = http_mock.HttpMock(data=b'access_token=SlAV32hkKG&expires=3600') credentials = self.flow.step2_exchange(code='some random code', http=http) self.assertNotEqual(None, credentials.token_expiry) @@ -2004,18 +1967,16 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): b' "access_token":"SlAV32hkKG",' b' "refresh_token":"8xLOxBtZp8"' b'}') - http = HttpMockSequence([({'status': '200'}, payload)]) + http = http_mock.HttpMock(data=payload) credentials = self.flow.step2_exchange(code='some random code', http=http) self.assertEqual(None, credentials.token_expiry) def test_urlencoded_exchange_no_expires_in(self): - http = HttpMockSequence([ - # This might be redundant but just to make sure - # urlencoded access_token gets parsed correctly - ({'status': '200'}, b'access_token=SlAV32hkKG'), - ]) + # This might be redundant but just to make sure + # urlencoded access_token gets parsed correctly + http = http_mock.HttpMock(data=b'access_token=SlAV32hkKG') credentials = self.flow.step2_exchange(code='some random code', http=http) @@ -2026,7 +1987,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): b' "access_token":"SlAV32hkKG",' b' "refresh_token":"8xLOxBtZp8"' b'}') - http = HttpMockSequence([({'status': '200'}, payload)]) + http = http_mock.HttpMock(data=payload) code = {'error': 'thou shall not pass'} with self.assertRaisesRegexp( @@ -2039,7 +2000,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): b' "refresh_token":"8xLOxBtZp8",' b' "id_token": "stuff.payload"' b'}') - http = HttpMockSequence([({'status': '200'}, payload)]) + http = http_mock.HttpMock(data=payload) with self.assertRaises(client.VerifyJwtTokenError): self.flow.step2_exchange(code='some random code', http=http) @@ -2056,7 +2017,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): b' "refresh_token":"8xLOxBtZp8",' b' "id_token": "' + jwt + b'"' b'}') - http = HttpMockSequence([({'status': '200'}, payload)]) + http = http_mock.HttpMock(data=payload) credentials = self.flow.step2_exchange(code='some random code', http=http) self.assertEqual(credentials.id_token, body) @@ -2065,7 +2026,7 @@ class OAuth2WebServerFlowTest(unittest2.TestCase): class FlowFromCachedClientsecrets(unittest2.TestCase): def test_flow_from_clientsecrets_cached(self): - cache_mock = CacheMock() + cache_mock = http_mock.CacheMock() load_and_cache('client_secrets.json', 'some_secrets', cache_mock) flow = client.flow_from_clientsecrets( @@ -2180,9 +2141,7 @@ class CredentialsFromCodeTests(unittest2.TestCase): def test_exchange_code_for_token(self): token = 'asdfghjkl' payload = json.dumps({'access_token': token, 'expires_in': 3600}) - http = HttpMockSequence([ - ({'status': '200'}, payload.encode('utf-8')), - ]) + http = http_mock.HttpMock(data=payload.encode('utf-8')) credentials = client.credentials_from_code( self.client_id, self.client_secret, self.scope, self.code, http=http, redirect_uri=self.redirect_uri) @@ -2191,9 +2150,10 @@ class CredentialsFromCodeTests(unittest2.TestCase): self.assertEqual(set(['foo']), credentials.scopes) def test_exchange_code_for_token_fail(self): - http = HttpMockSequence([ - ({'status': '400'}, b'{"error":"invalid_request"}'), - ]) + http = http_mock.HttpMock( + headers={'status': http_client.BAD_REQUEST}, + data=b'{"error":"invalid_request"}', + ) with self.assertRaises(client.FlowExchangeError): client.credentials_from_code( @@ -2205,7 +2165,7 @@ class CredentialsFromCodeTests(unittest2.TestCase): b' "access_token":"asdfghjkl",' b' "expires_in":3600' b'}') - http = HttpMockSequence([({'status': '200'}, payload)]) + http = http_mock.HttpMock(data=payload) credentials = client.credentials_from_clientsecrets_and_code( datafile('client_secrets.json'), self.scope, self.code, http=http) @@ -2214,10 +2174,8 @@ class CredentialsFromCodeTests(unittest2.TestCase): self.assertEqual(set(['foo']), credentials.scopes) def test_exchange_code_and_cached_file_for_token(self): - http = HttpMockSequence([ - ({'status': '200'}, b'{ "access_token":"asdfghjkl"}'), - ]) - cache_mock = CacheMock() + http = http_mock.HttpMock(data=b'{ "access_token":"asdfghjkl"}') + cache_mock = http_mock.CacheMock() load_and_cache('client_secrets.json', 'some_secrets', cache_mock) credentials = client.credentials_from_clientsecrets_and_code( @@ -2227,9 +2185,10 @@ class CredentialsFromCodeTests(unittest2.TestCase): self.assertEqual(set(['foo']), credentials.scopes) def test_exchange_code_and_file_for_token_fail(self): - http = HttpMockSequence([ - ({'status': '400'}, b'{"error":"invalid_request"}'), - ]) + http = http_mock.HttpMock( + headers={'status': http_client.BAD_REQUEST}, + data=b'{"error":"invalid_request"}', + ) with self.assertRaises(client.FlowExchangeError): client.credentials_from_clientsecrets_and_code( diff --git a/tests/test_service_account.py b/tests/test_service_account.py index 699e699..755a79b 100644 --- a/tests/test_service_account.py +++ b/tests/test_service_account.py @@ -22,7 +22,6 @@ import json import os import tempfile -import httplib2 import mock import rsa from six import BytesIO @@ -488,9 +487,10 @@ class JWTAccessCredentialsTests(unittest2.TestCase): self.assertEqual(payload['exp'], T1_EXPIRY) self.assertEqual(uri, self.url) self.assertEqual(bearer, b'Bearer') - return (httplib2.Response({'status': '200'}), b'') + response = mock.Mock(status=200) + return response, b'' - h = httplib2.Http() + h = mock.Mock() h.request = mock_request self.jwt.authorize(h) h.request(self.url) @@ -523,9 +523,10 @@ class JWTAccessCredentialsTests(unittest2.TestCase): self.assertEqual(payload['exp'], T1_EXPIRY) self.assertEqual(uri, self.url) self.assertEqual(bearer, b'Bearer') - return httplib2.Response({'status': '200'}), b'' + response = mock.Mock(status=200) + return response, b'' - h = httplib2.Http() + h = mock.Mock() h.request = mock_request jwt.authorize(h) h.request(self.url)