| # Copyright 2015 Google Inc. All Rights Reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Tests for oauth2client.contrib.devshell.""" | |
| import datetime | |
| import json | |
| import os | |
| import socket | |
| import threading | |
| import mock | |
| import unittest2 | |
| from oauth2client import _helpers | |
| from oauth2client import client | |
| from oauth2client.contrib import devshell | |
| # A dummy value to use for the expires_in field | |
| # in CredentialInfoResponse. | |
| EXPIRES_IN = 1000 | |
| DEFAULT_CREDENTIAL_JSON = json.dumps([ | |
| 'joe@example.com', | |
| 'fooproj', | |
| 'sometoken', | |
| EXPIRES_IN | |
| ]) | |
| class TestCredentialInfoResponse(unittest2.TestCase): | |
| def test_constructor_with_non_list(self): | |
| json_non_list = '{}' | |
| with self.assertRaises(ValueError): | |
| devshell.CredentialInfoResponse(json_non_list) | |
| def test_constructor_with_bad_json(self): | |
| json_non_list = '{BADJSON' | |
| with self.assertRaises(ValueError): | |
| devshell.CredentialInfoResponse(json_non_list) | |
| def test_constructor_empty_list(self): | |
| info_response = devshell.CredentialInfoResponse('[]') | |
| self.assertEqual(info_response.user_email, None) | |
| self.assertEqual(info_response.project_id, None) | |
| self.assertEqual(info_response.access_token, None) | |
| self.assertEqual(info_response.expires_in, None) | |
| def test_constructor_full_list(self): | |
| user_email = 'user_email' | |
| project_id = 'project_id' | |
| access_token = 'access_token' | |
| expires_in = 1 | |
| json_string = json.dumps( | |
| [user_email, project_id, access_token, expires_in]) | |
| info_response = devshell.CredentialInfoResponse(json_string) | |
| self.assertEqual(info_response.user_email, user_email) | |
| self.assertEqual(info_response.project_id, project_id) | |
| self.assertEqual(info_response.access_token, access_token) | |
| self.assertEqual(info_response.expires_in, expires_in) | |
| class Test_SendRecv(unittest2.TestCase): | |
| def test_port_zero(self): | |
| with mock.patch('oauth2client.contrib.devshell.os') as os_mod: | |
| os_mod.getenv = mock.MagicMock(name='getenv', return_value=0) | |
| with self.assertRaises(devshell.NoDevshellServer): | |
| devshell._SendRecv() | |
| os_mod.getenv.assert_called_once_with(devshell.DEVSHELL_ENV, 0) | |
| def test_no_newline_in_received_header(self): | |
| non_zero_port = 1 | |
| sock = mock.MagicMock() | |
| header_without_newline = '' | |
| sock.recv(6).decode = mock.MagicMock( | |
| name='decode', return_value=header_without_newline) | |
| with mock.patch('oauth2client.contrib.devshell.os') as os_mod: | |
| os_mod.getenv = mock.MagicMock(name='getenv', | |
| return_value=non_zero_port) | |
| with mock.patch('oauth2client.contrib.devshell.socket') as socket: | |
| socket.socket = mock.MagicMock(name='socket', | |
| return_value=sock) | |
| with self.assertRaises(devshell.CommunicationError): | |
| devshell._SendRecv() | |
| os_mod.getenv.assert_called_once_with(devshell.DEVSHELL_ENV, 0) | |
| socket.socket.assert_called_once_with() | |
| sock.recv(6).decode.assert_called_once_with() | |
| data = devshell.CREDENTIAL_INFO_REQUEST_JSON | |
| msg = _helpers._to_bytes( | |
| '{0}\n{1}'.format(len(data), data), encoding='utf-8') | |
| expected_sock_calls = [ | |
| mock.call.recv(6), # From the set-up above | |
| mock.call.connect(('localhost', non_zero_port)), | |
| mock.call.sendall(msg), | |
| mock.call.recv(6), | |
| mock.call.recv(6), # From the check above | |
| ] | |
| self.assertEqual(sock.method_calls, expected_sock_calls) | |
| class _AuthReferenceServer(threading.Thread): | |
| def __init__(self, response=None): | |
| super(_AuthReferenceServer, self).__init__(None) | |
| self.response = response or DEFAULT_CREDENTIAL_JSON | |
| self.bad_request = False | |
| def __enter__(self): | |
| return self.start_server() | |
| def start_server(self): | |
| self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| self._socket.bind(('localhost', 0)) | |
| port = self._socket.getsockname()[1] | |
| os.environ[devshell.DEVSHELL_ENV] = str(port) | |
| self._socket.listen(0) | |
| self.daemon = True | |
| self.start() | |
| return self | |
| def __exit__(self, e_type, value, traceback): | |
| self.stop_server() | |
| def stop_server(self): | |
| del os.environ[devshell.DEVSHELL_ENV] | |
| self._socket.close() | |
| def run(self): | |
| s = None | |
| try: | |
| # Do not set the timeout on the socket, leave it in the blocking | |
| # mode as setting the timeout seems to cause spurious EAGAIN | |
| # errors on OSX. | |
| self._socket.settimeout(None) | |
| s, unused_addr = self._socket.accept() | |
| resp_buffer = '' | |
| resp_1 = s.recv(6).decode() | |
| nstr, extra = resp_1.split('\n', 1) | |
| resp_buffer = extra | |
| n = int(nstr) | |
| to_read = n - len(extra) | |
| if to_read > 0: | |
| resp_buffer += _helpers._from_bytes( | |
| s.recv(to_read, socket.MSG_WAITALL)) | |
| if resp_buffer != devshell.CREDENTIAL_INFO_REQUEST_JSON: | |
| self.bad_request = True | |
| l = len(self.response) | |
| s.sendall('{0}\n{1}'.format(l, self.response).encode()) | |
| finally: | |
| # Will fail if s is None, but these tests never encounter | |
| # that scenario. | |
| s.close() | |
| class DevshellCredentialsTests(unittest2.TestCase): | |
| def test_signals_no_server(self): | |
| with self.assertRaises(devshell.NoDevshellServer): | |
| devshell.DevshellCredentials() | |
| def test_bad_message_to_mock_server(self): | |
| request_content = devshell.CREDENTIAL_INFO_REQUEST_JSON + 'extrastuff' | |
| request_message = _helpers._to_bytes( | |
| '{0}\n{1}'.format(len(request_content), request_content)) | |
| response_message = 'foobar' | |
| with _AuthReferenceServer(response_message) as auth_server: | |
| self.assertFalse(auth_server.bad_request) | |
| sock = socket.socket() | |
| port = int(os.getenv(devshell.DEVSHELL_ENV, 0)) | |
| sock.connect(('localhost', port)) | |
| sock.sendall(request_message) | |
| # Mimic the receive part of _SendRecv | |
| header = sock.recv(6).decode() | |
| len_str, result = header.split('\n', 1) | |
| to_read = int(len_str) - len(result) | |
| result += sock.recv(to_read, socket.MSG_WAITALL).decode() | |
| self.assertTrue(auth_server.bad_request) | |
| self.assertEqual(result, response_message) | |
| def test_request_response(self): | |
| with _AuthReferenceServer(): | |
| response = devshell._SendRecv() | |
| self.assertEqual(response.user_email, 'joe@example.com') | |
| self.assertEqual(response.project_id, 'fooproj') | |
| self.assertEqual(response.access_token, 'sometoken') | |
| def test_no_refresh_token(self): | |
| with _AuthReferenceServer(): | |
| creds = devshell.DevshellCredentials() | |
| self.assertEquals(None, creds.refresh_token) | |
| @mock.patch('oauth2client.client._UTCNOW') | |
| def test_reads_credentials(self, utcnow): | |
| NOW = datetime.datetime(1992, 12, 31) | |
| utcnow.return_value = NOW | |
| with _AuthReferenceServer(): | |
| creds = devshell.DevshellCredentials() | |
| self.assertEqual('joe@example.com', creds.user_email) | |
| self.assertEqual('fooproj', creds.project_id) | |
| self.assertEqual('sometoken', creds.access_token) | |
| self.assertEqual( | |
| NOW + datetime.timedelta(seconds=EXPIRES_IN), | |
| creds.token_expiry) | |
| utcnow.assert_called_once_with() | |
| def test_handles_skipped_fields(self): | |
| with _AuthReferenceServer('["joe@example.com"]'): | |
| creds = devshell.DevshellCredentials() | |
| self.assertEqual('joe@example.com', creds.user_email) | |
| self.assertEqual(None, creds.project_id) | |
| self.assertEqual(None, creds.access_token) | |
| self.assertEqual(None, creds.token_expiry) | |
| def test_handles_tiny_response(self): | |
| with _AuthReferenceServer('[]'): | |
| creds = devshell.DevshellCredentials() | |
| self.assertEqual(None, creds.user_email) | |
| self.assertEqual(None, creds.project_id) | |
| self.assertEqual(None, creds.access_token) | |
| def test_handles_ignores_extra_fields(self): | |
| with _AuthReferenceServer( | |
| '["joe@example.com", "fooproj", "sometoken", 1, "extra"]'): | |
| creds = devshell.DevshellCredentials() | |
| self.assertEqual('joe@example.com', creds.user_email) | |
| self.assertEqual('fooproj', creds.project_id) | |
| self.assertEqual('sometoken', creds.access_token) | |
| def test_refuses_to_save_to_well_known_file(self): | |
| ORIGINAL_ISDIR = os.path.isdir | |
| try: | |
| os.path.isdir = lambda path: True | |
| with _AuthReferenceServer(): | |
| creds = devshell.DevshellCredentials() | |
| with self.assertRaises(NotImplementedError): | |
| client.save_to_well_known_file(creds) | |
| finally: | |
| os.path.isdir = ORIGINAL_ISDIR | |
| def test_from_json(self): | |
| with self.assertRaises(NotImplementedError): | |
| devshell.DevshellCredentials.from_json(None) | |
| def test_serialization_data(self): | |
| with _AuthReferenceServer('[]'): | |
| credentials = devshell.DevshellCredentials() | |
| with self.assertRaises(NotImplementedError): | |
| getattr(credentials, 'serialization_data') |