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 |