| OLD | NEW |
| (Empty) |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import os | |
| 6 import socket | |
| 7 import time | |
| 8 import unittest | |
| 9 | |
| 10 import infra_libs | |
| 11 from infra_libs.ts_mon.common import http_metrics | |
| 12 from infra_libs import httplib2_utils | |
| 13 from infra_libs import ts_mon | |
| 14 | |
| 15 import httplib2 | |
| 16 import mock | |
| 17 import oauth2client.client | |
| 18 | |
| 19 | |
| 20 DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data') | |
| 21 | |
| 22 | |
| 23 class LoadJsonCredentialsTest(unittest.TestCase): | |
| 24 # Everything's good, should not raise any exceptions. | |
| 25 def test_valid_credentials(self): | |
| 26 creds = httplib2_utils.load_service_account_credentials( | |
| 27 'valid_creds.json', | |
| 28 service_accounts_creds_root=DATA_DIR) | |
| 29 self.assertIsInstance(creds, dict) | |
| 30 self.assertIn('type', creds) | |
| 31 self.assertIn('client_email', creds) | |
| 32 self.assertIn('private_key', creds) | |
| 33 | |
| 34 # File exists but issue with the content: raises AuthError. | |
| 35 def test_missing_type(self): | |
| 36 with self.assertRaises(infra_libs.AuthError): | |
| 37 httplib2_utils.load_service_account_credentials( | |
| 38 'creds_missing_type.json', | |
| 39 service_accounts_creds_root=DATA_DIR) | |
| 40 | |
| 41 def test_wrong_type(self): | |
| 42 with self.assertRaises(infra_libs.AuthError): | |
| 43 httplib2_utils.load_service_account_credentials( | |
| 44 'creds_wrong_type.json', | |
| 45 service_accounts_creds_root=DATA_DIR) | |
| 46 | |
| 47 def test_missing_client_email(self): | |
| 48 with self.assertRaises(infra_libs.AuthError): | |
| 49 httplib2_utils.load_service_account_credentials( | |
| 50 'creds_missing_client_email.json', | |
| 51 service_accounts_creds_root=DATA_DIR) | |
| 52 | |
| 53 def test_missing_private_key(self): | |
| 54 with self.assertRaises(infra_libs.AuthError): | |
| 55 httplib2_utils.load_service_account_credentials( | |
| 56 'creds_missing_private_key.json', | |
| 57 service_accounts_creds_root=DATA_DIR) | |
| 58 | |
| 59 def test_malformed(self): | |
| 60 with self.assertRaises(infra_libs.AuthError): | |
| 61 httplib2_utils.load_service_account_credentials( | |
| 62 'creds_malformed.json', | |
| 63 service_accounts_creds_root=DATA_DIR) | |
| 64 | |
| 65 # Problem with the file itself | |
| 66 def test_file_not_found(self): | |
| 67 with self.assertRaises(IOError): | |
| 68 httplib2_utils.load_service_account_credentials( | |
| 69 'this_file_should_not_exist.json', | |
| 70 service_accounts_creds_root=DATA_DIR) | |
| 71 | |
| 72 | |
| 73 class GetSignedJwtAssertionCredentialsTest(unittest.TestCase): | |
| 74 def test_valid_credentials(self): | |
| 75 creds = infra_libs.get_signed_jwt_assertion_credentials( | |
| 76 'valid_creds.json', | |
| 77 service_accounts_creds_root=DATA_DIR) | |
| 78 self.assertIsInstance(creds, | |
| 79 oauth2client.client.SignedJwtAssertionCredentials) | |
| 80 # A default scope must be provided, we don't care which one | |
| 81 self.assertTrue(creds.scope) | |
| 82 | |
| 83 def test_valid_credentials_with_scope_as_string(self): | |
| 84 creds = infra_libs.get_signed_jwt_assertion_credentials( | |
| 85 'valid_creds.json', | |
| 86 scope='repo', | |
| 87 service_accounts_creds_root=DATA_DIR) | |
| 88 self.assertIsInstance(creds, | |
| 89 oauth2client.client.SignedJwtAssertionCredentials) | |
| 90 self.assertIn('repo', creds.scope) | |
| 91 | |
| 92 def test_valid_credentials_with_scope_as_list(self): | |
| 93 creds = infra_libs.get_signed_jwt_assertion_credentials( | |
| 94 'valid_creds.json', | |
| 95 scope=['gist'], | |
| 96 service_accounts_creds_root=DATA_DIR) | |
| 97 self.assertIsInstance(creds, | |
| 98 oauth2client.client.SignedJwtAssertionCredentials) | |
| 99 self.assertIn('gist', creds.scope) | |
| 100 | |
| 101 # Only test one malformed case and rely on LoadJsonCredentialsTest | |
| 102 # for the other cases. | |
| 103 def test_malformed_credentials(self): | |
| 104 with self.assertRaises(infra_libs.AuthError): | |
| 105 infra_libs.get_signed_jwt_assertion_credentials( | |
| 106 'creds_malformed.json', | |
| 107 service_accounts_creds_root=DATA_DIR) | |
| 108 | |
| 109 | |
| 110 class GetAuthenticatedHttp(unittest.TestCase): | |
| 111 def test_valid_credentials(self): | |
| 112 http = infra_libs.get_authenticated_http( | |
| 113 'valid_creds.json', | |
| 114 service_accounts_creds_root=DATA_DIR) | |
| 115 self.assertIsInstance(http, httplib2.Http) | |
| 116 | |
| 117 def test_valid_credentials_authenticated(self): | |
| 118 http = infra_libs.get_authenticated_http( | |
| 119 'valid_creds.json', | |
| 120 service_accounts_creds_root=DATA_DIR, | |
| 121 http_identifier='test_case') | |
| 122 self.assertIsInstance(http, infra_libs.InstrumentedHttp) | |
| 123 | |
| 124 # Only test one malformed case and rely on LoadJsonCredentialsTest | |
| 125 # for the other cases. | |
| 126 def test_malformed_credentials(self): | |
| 127 with self.assertRaises(infra_libs.AuthError): | |
| 128 infra_libs.get_authenticated_http( | |
| 129 'creds_malformed.json', | |
| 130 service_accounts_creds_root=DATA_DIR) | |
| 131 | |
| 132 class RetriableHttplib2Test(unittest.TestCase): | |
| 133 def setUp(self): | |
| 134 super(RetriableHttplib2Test, self).setUp() | |
| 135 self.http = infra_libs.RetriableHttp(httplib2.Http()) | |
| 136 self.http._http.request = mock.create_autospec(self.http._http.request, | |
| 137 spec_set=True) | |
| 138 | |
| 139 _MOCK_REQUEST = mock.call('http://foo/', 'GET', None) | |
| 140 | |
| 141 def test_authorize(self): | |
| 142 http = infra_libs.RetriableHttp(httplib2.Http()) | |
| 143 creds = infra_libs.get_signed_jwt_assertion_credentials( | |
| 144 'valid_creds.json', | |
| 145 service_accounts_creds_root=DATA_DIR) | |
| 146 creds.authorize(http) | |
| 147 | |
| 148 def test_delegate_get_attr(self): | |
| 149 """RetriableHttp should delegate getting attribute except request() to | |
| 150 Http""" | |
| 151 self.http._http.clear_credentials = mock.create_autospec( | |
| 152 self.http._http.clear_credentials, spec_set=True) | |
| 153 self.http.clear_credentials() | |
| 154 self.http._http.clear_credentials.assert_called_once_with() | |
| 155 | |
| 156 def test_delegate_set_attr(self): | |
| 157 """RetriableHttp should delegate setting attributes to Http""" | |
| 158 self.http.ignore_etag = False | |
| 159 self.assertFalse(self.http.ignore_etag) | |
| 160 self.assertFalse(self.http._http.ignore_etag) | |
| 161 self.http.ignore_etag = True | |
| 162 self.assertTrue(self.http.ignore_etag) | |
| 163 self.assertTrue(self.http._http.ignore_etag) | |
| 164 | |
| 165 @mock.patch('time.sleep', autospec=True) | |
| 166 def test_succeed(self, _sleep): | |
| 167 self.http._http.request.return_value = ( | |
| 168 httplib2.Response({'status': 400}), 'content') | |
| 169 response, _ = self.http.request('http://foo/') | |
| 170 self.assertEqual(400, response.status) | |
| 171 self.http._http.request.assert_has_calls([ self._MOCK_REQUEST ]) | |
| 172 | |
| 173 @mock.patch('time.sleep', autospec=True) | |
| 174 def test_retry_succeed(self, _sleep): | |
| 175 self.http._http.request.side_effect = iter([ | |
| 176 (httplib2.Response({'status': 500}), 'content'), | |
| 177 httplib2.HttpLib2Error, | |
| 178 (httplib2.Response({'status': 200}), 'content') | |
| 179 ]) | |
| 180 response, _ = self.http.request('http://foo/') | |
| 181 self.assertEqual(200, response.status) | |
| 182 self.http._http.request.assert_has_calls([ self._MOCK_REQUEST ] * 3) | |
| 183 | |
| 184 @mock.patch('time.sleep', autospec=True) | |
| 185 def test_fail_exception(self, _sleep): | |
| 186 self.http._http.request.side_effect = httplib2.HttpLib2Error() | |
| 187 self.assertRaises(httplib2.HttpLib2Error, self.http.request, 'http://foo/') | |
| 188 self.http._http.request.assert_has_calls([ self._MOCK_REQUEST ] * 5) | |
| 189 | |
| 190 @mock.patch('time.sleep', autospec=True) | |
| 191 def test_fail_status_code(self, _sleep): | |
| 192 self.http._http.request.return_value = ( | |
| 193 httplib2.Response({'status': 500}), 'content') | |
| 194 response, _ = self.http.request('http://foo/') | |
| 195 self.assertEqual(500, response.status) | |
| 196 self.http._http.request.assert_has_calls([ self._MOCK_REQUEST ] * 5) | |
| 197 | |
| 198 | |
| 199 class InstrumentedHttplib2Test(unittest.TestCase): | |
| 200 def setUp(self): | |
| 201 super(InstrumentedHttplib2Test, self).setUp() | |
| 202 self.mock_time = mock.create_autospec(time.time, spec_set=True) | |
| 203 self.mock_time.return_value = 42 | |
| 204 self.http = infra_libs.InstrumentedHttp('test', time_fn=self.mock_time) | |
| 205 self.http._request = mock.Mock() | |
| 206 ts_mon.reset_for_unittest() | |
| 207 | |
| 208 def test_success_status(self): | |
| 209 self.http._request.return_value = ( | |
| 210 httplib2.Response({'status': 200}), | |
| 211 'content') | |
| 212 | |
| 213 response, _ = self.http.request('http://foo/') | |
| 214 self.assertEqual(200, response.status) | |
| 215 self.assertEqual(1, http_metrics.response_status.get( | |
| 216 {'name': 'test', 'client': 'httplib2', 'status': 200})) | |
| 217 self.assertIsNone(http_metrics.response_status.get( | |
| 218 {'name': 'test', 'client': 'httplib2', 'status': 404})) | |
| 219 | |
| 220 def test_error_status(self): | |
| 221 self.http._request.return_value = ( | |
| 222 httplib2.Response({'status': 404}), | |
| 223 'content') | |
| 224 | |
| 225 response, _ = self.http.request('http://foo/') | |
| 226 self.assertEqual(404, response.status) | |
| 227 self.assertIsNone(http_metrics.response_status.get( | |
| 228 {'name': 'test', 'client': 'httplib2', 'status': 200})) | |
| 229 self.assertEqual(1, http_metrics.response_status.get( | |
| 230 {'name': 'test', 'client': 'httplib2', 'status': 404})) | |
| 231 | |
| 232 def test_timeout(self): | |
| 233 self.http._request.side_effect = socket.timeout | |
| 234 | |
| 235 with self.assertRaises(socket.timeout): | |
| 236 self.http.request('http://foo/') | |
| 237 self.assertIsNone(http_metrics.response_status.get( | |
| 238 {'name': 'test', 'client': 'httplib2', 'status': 200})) | |
| 239 self.assertEqual(1, http_metrics.response_status.get( | |
| 240 {'name': 'test', 'client': 'httplib2', | |
| 241 'status': http_metrics.STATUS_TIMEOUT})) | |
| 242 | |
| 243 def test_connection_error(self): | |
| 244 self.http._request.side_effect = socket.error | |
| 245 | |
| 246 with self.assertRaises(socket.error): | |
| 247 self.http.request('http://foo/') | |
| 248 self.assertIsNone(http_metrics.response_status.get( | |
| 249 {'name': 'test', 'client': 'httplib2', 'status': 200})) | |
| 250 self.assertEqual(1, http_metrics.response_status.get( | |
| 251 {'name': 'test', 'client': 'httplib2', | |
| 252 'status': http_metrics.STATUS_ERROR})) | |
| 253 | |
| 254 def test_exception(self): | |
| 255 self.http._request.side_effect = httplib2.HttpLib2Error | |
| 256 | |
| 257 with self.assertRaises(httplib2.HttpLib2Error): | |
| 258 self.http.request('http://foo/') | |
| 259 self.assertIsNone(http_metrics.response_status.get( | |
| 260 {'name': 'test', 'client': 'httplib2', 'status': 200})) | |
| 261 self.assertEqual(1, http_metrics.response_status.get( | |
| 262 {'name': 'test', 'client': 'httplib2', | |
| 263 'status': http_metrics.STATUS_EXCEPTION})) | |
| 264 | |
| 265 def test_response_bytes(self): | |
| 266 self.http._request.return_value = ( | |
| 267 httplib2.Response({'status': 200}), | |
| 268 'content') | |
| 269 | |
| 270 _, content = self.http.request('http://foo/') | |
| 271 self.assertEqual('content', content) | |
| 272 self.assertEqual(1, http_metrics.response_bytes.get( | |
| 273 {'name': 'test', 'client': 'httplib2'}).count) | |
| 274 self.assertEqual(7, http_metrics.response_bytes.get( | |
| 275 {'name': 'test', 'client': 'httplib2'}).sum) | |
| 276 | |
| 277 def test_request_bytes(self): | |
| 278 self.http._request.return_value = ( | |
| 279 httplib2.Response({'status': 200}), | |
| 280 'content') | |
| 281 | |
| 282 _, content = self.http.request('http://foo/', body='wibblewibble') | |
| 283 self.assertEqual('content', content) | |
| 284 self.assertEqual(1, http_metrics.request_bytes.get( | |
| 285 {'name': 'test', 'client': 'httplib2'}).count) | |
| 286 self.assertEqual(12, http_metrics.request_bytes.get( | |
| 287 {'name': 'test', 'client': 'httplib2'}).sum) | |
| 288 | |
| 289 def test_duration(self): | |
| 290 current_time = [4.2] | |
| 291 | |
| 292 def time_side_effect(): | |
| 293 ret = current_time[0] | |
| 294 current_time[0] += 0.3 | |
| 295 return ret | |
| 296 self.mock_time.side_effect = time_side_effect | |
| 297 | |
| 298 self.http._request.return_value = ( | |
| 299 httplib2.Response({'status': 200}), | |
| 300 'content') | |
| 301 | |
| 302 _, _ = self.http.request('http://foo/') | |
| 303 self.assertEqual(1, http_metrics.durations.get( | |
| 304 {'name': 'test', 'client': 'httplib2'}).count) | |
| 305 self.assertAlmostEqual(300, http_metrics.durations.get( | |
| 306 {'name': 'test', 'client': 'httplib2'}).sum) | |
| 307 | |
| 308 | |
| 309 class HttpMockTest(unittest.TestCase): | |
| 310 def test_empty(self): | |
| 311 http = infra_libs.HttpMock([]) | |
| 312 with self.assertRaises(AssertionError): | |
| 313 http.request('https://www.google.com', 'GET') | |
| 314 | |
| 315 def test_invalid_parameter(self): | |
| 316 with self.assertRaises(TypeError): | |
| 317 infra_libs.HttpMock(None) | |
| 318 | |
| 319 def test_uris_wrong_length(self): | |
| 320 with self.assertRaises(ValueError): | |
| 321 infra_libs.HttpMock([(1, 2)]) | |
| 322 | |
| 323 def test_uris_wrong_type(self): | |
| 324 with self.assertRaises(ValueError): | |
| 325 infra_libs.HttpMock([(None,)]) | |
| 326 | |
| 327 def test_invalid_uri(self): | |
| 328 with self.assertRaises(TypeError): | |
| 329 infra_libs.HttpMock([(1, {'status': '100'}, None)]) | |
| 330 | |
| 331 def test_invalid_headers(self): | |
| 332 with self.assertRaises(TypeError): | |
| 333 infra_libs.HttpMock([('https://www.google.com', None, None)]) | |
| 334 | |
| 335 def test_headers_without_status(self): | |
| 336 with self.assertRaises(ValueError): | |
| 337 infra_libs.HttpMock([('https://www.google.com', {'foo': 'bar'}, None)]) | |
| 338 | |
| 339 def test_invalid_body(self): | |
| 340 with self.assertRaises(TypeError): | |
| 341 infra_libs.HttpMock([('https://www.google.com', {'status': '200'}, 42)]) | |
| 342 | |
| 343 def test_one_uri(self): | |
| 344 http = infra_libs.HttpMock([('https://www.google.com', | |
| 345 {'status': '403'}, | |
| 346 'bar')]) | |
| 347 response, body = http.request('https://www.google.com', 'GET') | |
| 348 self.assertIsInstance(response, httplib2.Response) | |
| 349 self.assertEqual(response.status, 403) | |
| 350 self.assertEqual(body, 'bar') | |
| 351 | |
| 352 def test_two_uris(self): | |
| 353 http = infra_libs.HttpMock([('https://www.google.com', | |
| 354 {'status': 200}, 'foo'), | |
| 355 ('.*', {'status': 404}, '')]) | |
| 356 response, body = http.request('https://mywebserver.woo.hoo', 'GET') | |
| 357 self.assertIsInstance(response, httplib2.Response) | |
| 358 self.assertEqual(response.status, 404) | |
| 359 self.assertEqual(body, '') | |
| 360 | |
| 361 self.assertEqual(http.requests_made[0].uri, 'https://mywebserver.woo.hoo') | |
| 362 self.assertEqual(http.requests_made[0].method, 'GET') | |
| 363 self.assertEqual(http.requests_made[0].body, None) | |
| 364 self.assertEqual(http.requests_made[0].headers, None) | |
| OLD | NEW |