blob: fe746726309dbb7ae37836ed0238cf93aaeccbe9 [file] [log] [blame]
Craig Citro15744b12015-03-02 13:34:32 -08001#!/usr/bin/env python
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05002#
Craig Citro751b7fb2014-09-23 11:20:38 -07003# Copyright 2014 Google Inc. All Rights Reserved.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Http tests
18
John Asmuth864311d2014-04-24 15:46:08 -040019Unit tests for the googleapiclient.http.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050020"""
INADA Naokid898a372015-03-04 03:52:46 +090021from __future__ import absolute_import
22from six.moves import range
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050023
24__author__ = 'jcgregorio@google.com (Joe Gregorio)'
25
Pat Feratec6050872015-03-03 18:24:59 -080026from six import PY3
Pat Ferateed9affd2015-03-03 16:03:15 -080027from six import BytesIO, StringIO
28from io import FileIO
Pat Ferated5b61bd2015-03-03 16:04:11 -080029from six.moves.urllib.parse import urlencode
Pat Ferateed9affd2015-03-03 16:03:15 -080030
Joe Gregorio7cbceab2011-06-27 10:46:54 -040031# Do not remove the httplib2 import
32import httplib2
Joe Gregorio9086bd32013-06-14 16:32:05 -040033import logging
eesheeshc6425a02016-02-12 15:07:06 +000034import mock
Joe Gregoriod0bd3882011-11-22 09:49:47 -050035import os
Pat Ferate497a90f2015-03-09 09:52:54 -070036import unittest2 as unittest
Joe Gregorio9086bd32013-06-14 16:32:05 -040037import random
eesheeshc6425a02016-02-12 15:07:06 +000038import socket
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +010039import ssl
Joe Gregorio9086bd32013-06-14 16:32:05 -040040import time
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050041
John Asmuth864311d2014-04-24 15:46:08 -040042from googleapiclient.discovery import build
43from googleapiclient.errors import BatchError
44from googleapiclient.errors import HttpError
45from googleapiclient.errors import InvalidChunkSizeError
Igor Maravić22435292017-01-19 22:28:22 +010046from googleapiclient.http import build_http
John Asmuth864311d2014-04-24 15:46:08 -040047from googleapiclient.http import BatchHttpRequest
48from googleapiclient.http import HttpMock
49from googleapiclient.http import HttpMockSequence
50from googleapiclient.http import HttpRequest
51from googleapiclient.http import MAX_URI_LENGTH
52from googleapiclient.http import MediaFileUpload
53from googleapiclient.http import MediaInMemoryUpload
54from googleapiclient.http import MediaIoBaseDownload
55from googleapiclient.http import MediaIoBaseUpload
56from googleapiclient.http import MediaUpload
57from googleapiclient.http import _StreamSlice
58from googleapiclient.http import set_user_agent
59from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050060from oauth2client.client import Credentials
61
62
63class MockCredentials(Credentials):
64 """Mock class for all Credentials objects."""
65 def __init__(self, bearer_token):
66 super(MockCredentials, self).__init__()
67 self._authorized = 0
68 self._refreshed = 0
69 self._applied = 0
70 self._bearer_token = bearer_token
71
72 def authorize(self, http):
73 self._authorized += 1
74
75 request_orig = http.request
76
77 # The closure that will replace 'httplib2.Http.request'.
78 def new_request(uri, method='GET', body=None, headers=None,
79 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
80 connection_type=None):
81 # Modify the request headers to add the appropriate
82 # Authorization header.
83 if headers is None:
84 headers = {}
85 self.apply(headers)
86
87 resp, content = request_orig(uri, method, body, headers,
88 redirections, connection_type)
89
90 return resp, content
91
92 # Replace the request method with our own closure.
93 http.request = new_request
94
95 # Set credentials as a property of the request method.
96 setattr(http.request, 'credentials', self)
97
98 return http
99
100 def refresh(self, http):
101 self._refreshed += 1
102
103 def apply(self, headers):
104 self._applied += 1
105 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500106
107
eesheeshc6425a02016-02-12 15:07:06 +0000108class HttpMockWithErrors(object):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100109 def __init__(self, num_errors, success_json, success_data):
110 self.num_errors = num_errors
111 self.success_json = success_json
112 self.success_data = success_data
113
114 def request(self, *args, **kwargs):
115 if not self.num_errors:
116 return httplib2.Response(self.success_json), self.success_data
117 else:
118 self.num_errors -= 1
eesheeshc6425a02016-02-12 15:07:06 +0000119 if self.num_errors == 1:
120 raise ssl.SSLError()
121 else:
122 if PY3:
123 ex = TimeoutError()
124 else:
125 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200126
127 if self.num_errors == 2:
128 #first try a broken pipe error (#218)
129 ex.errno = socket.errno.EPIPE
130 else:
131 # Initialize the timeout error code to the platform's error code.
132 try:
133 # For Windows:
134 ex.errno = socket.errno.WSAETIMEDOUT
135 except AttributeError:
136 # For Linux/Mac:
137 ex.errno = socket.errno.ETIMEDOUT
138 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000139 raise ex
140
141
142class HttpMockWithNonRetriableErrors(object):
143 def __init__(self, num_errors, success_json, success_data):
144 self.num_errors = num_errors
145 self.success_json = success_json
146 self.success_data = success_data
147
148 def request(self, *args, **kwargs):
149 if not self.num_errors:
150 return httplib2.Response(self.success_json), self.success_data
151 else:
152 self.num_errors -= 1
153 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200154 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000155 try:
156 # For Windows:
157 ex.errno = socket.errno.WSAECONNREFUSED
158 except AttributeError:
159 # For Linux/Mac:
160 ex.errno = socket.errno.ECONNREFUSED
161 # Now raise the correct timeout error.
162 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100163
164
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500165DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
166
167
168def datafile(filename):
169 return os.path.join(DATA_DIR, filename)
170
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500171class TestUserAgent(unittest.TestCase):
172
173 def test_set_user_agent(self):
174 http = HttpMockSequence([
175 ({'status': '200'}, 'echo_request_headers'),
176 ])
177
178 http = set_user_agent(http, "my_app/5.5")
179 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500180 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500181
182 def test_set_user_agent_nested(self):
183 http = HttpMockSequence([
184 ({'status': '200'}, 'echo_request_headers'),
185 ])
186
187 http = set_user_agent(http, "my_app/5.5")
188 http = set_user_agent(http, "my_library/0.1")
189 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500190 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500191
Joe Gregorio910b9b12012-06-12 09:36:30 -0400192
193class TestMediaUpload(unittest.TestCase):
194
Nam T. Nguyendc136312015-12-01 10:18:56 -0800195 def test_media_file_upload_mimetype_detection(self):
196 upload = MediaFileUpload(datafile('small.png'))
197 self.assertEqual('image/png', upload.mimetype())
198
199 upload = MediaFileUpload(datafile('empty'))
200 self.assertEqual('application/octet-stream', upload.mimetype())
201
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500202 def test_media_file_upload_to_from_json(self):
203 upload = MediaFileUpload(
204 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500205 self.assertEqual('image/png', upload.mimetype())
206 self.assertEqual(190, upload.size())
207 self.assertEqual(True, upload.resumable())
208 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800209 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500210
211 json = upload.to_json()
212 new_upload = MediaUpload.new_from_json(json)
213
Joe Gregorio654f4a22012-02-09 14:15:44 -0500214 self.assertEqual('image/png', new_upload.mimetype())
215 self.assertEqual(190, new_upload.size())
216 self.assertEqual(True, new_upload.resumable())
217 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800218 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500219
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400220 def test_media_file_upload_raises_on_invalid_chunksize(self):
221 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
222 datafile('small.png'), mimetype='image/png', chunksize=-2,
223 resumable=True)
224
Ali Afshar1cb6b672012-03-12 08:46:14 -0400225 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800226 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400227 resumable=True)
228 self.assertEqual('text/plain', media.mimetype())
229 self.assertEqual(10, media.chunksize())
230 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800231 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400232 self.assertEqual(6, media.size())
233
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500234 def test_http_request_to_from_json(self):
235
236 def _postproc(*kwargs):
237 pass
238
Igor Maravić22435292017-01-19 22:28:22 +0100239 http = build_http()
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500240 media_upload = MediaFileUpload(
241 datafile('small.png'), chunksize=500, resumable=True)
242 req = HttpRequest(
243 http,
244 _postproc,
245 'http://example.com',
246 method='POST',
247 body='{}',
248 headers={'content-type': 'multipart/related; boundary="---flubber"'},
249 methodId='foo',
250 resumable=media_upload)
251
252 json = req.to_json()
253 new_req = HttpRequest.from_json(json, http, _postproc)
254
Joe Gregorio654f4a22012-02-09 14:15:44 -0500255 self.assertEqual({'content-type':
256 'multipart/related; boundary="---flubber"'},
257 new_req.headers)
258 self.assertEqual('http://example.com', new_req.uri)
259 self.assertEqual('{}', new_req.body)
260 self.assertEqual(http, new_req.http)
261 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500262
Joe Gregorio9086bd32013-06-14 16:32:05 -0400263 self.assertEqual(random.random, new_req._rand)
264 self.assertEqual(time.sleep, new_req._sleep)
265
Joe Gregorio910b9b12012-06-12 09:36:30 -0400266
267class TestMediaIoBaseUpload(unittest.TestCase):
268
269 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800270 fd = FileIO(datafile('small.png'), 'r')
271 upload = MediaIoBaseUpload(
272 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
273 self.assertEqual('image/png', upload.mimetype())
274 self.assertEqual(190, upload.size())
275 self.assertEqual(True, upload.resumable())
276 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800277 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400278
279 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800280 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400281 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400282 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400283 self.assertEqual('image/png', upload.mimetype())
284 self.assertEqual(190, upload.size())
285 self.assertEqual(True, upload.resumable())
286 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800287 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400288 f.close()
289
290 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800291 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400292 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400293
294 try:
295 json = upload.to_json()
296 self.fail('MediaIoBaseUpload should not be serializable.')
297 except NotImplementedError:
298 pass
299
Pat Feratec6050872015-03-03 18:24:59 -0800300 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400301 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800302 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800303 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400304 f.close()
305
306 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400307 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400308 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400309 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400310 self.assertEqual(True, upload.resumable())
311 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800312 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400313 f.close()
314
315 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800316 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800317 fd = BytesIO(f.read())
318 upload = MediaIoBaseUpload(
319 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
320 self.assertEqual('image/png', upload.mimetype())
321 self.assertEqual(190, upload.size())
322 self.assertEqual(True, upload.resumable())
323 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800324 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400325
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400326 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800327 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800328 fd = BytesIO(f.read())
329 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
330 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400331
332 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800333 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800334 upload = MediaIoBaseUpload(
335 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
336 self.assertEqual(True, upload.has_stream())
337 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400338
Joe Gregorio9086bd32013-06-14 16:32:05 -0400339 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800340 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800341 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400342 upload = MediaIoBaseUpload(
343 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
344
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500345 # Simulate errors for both the request that creates the resumable upload
346 # and the upload itself.
Joe Gregorio9086bd32013-06-14 16:32:05 -0400347 http = HttpMockSequence([
348 ({'status': '500'}, ''),
349 ({'status': '500'}, ''),
350 ({'status': '503'}, ''),
351 ({'status': '200', 'location': 'location'}, ''),
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500352 ({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
353 ({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE),
354 ({'status': '429'}, ''),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400355 ({'status': '200'}, '{}'),
356 ])
357
358 model = JsonModel()
359 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
360 method = u'POST'
361 request = HttpRequest(
362 http,
363 model.response,
364 uri,
365 method=method,
366 headers={},
367 resumable=upload)
368
369 sleeptimes = []
370 request._sleep = lambda x: sleeptimes.append(x)
371 request._rand = lambda: 10
372
373 request.execute(num_retries=3)
374 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
375
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500376 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
377 fd = BytesIO(b"i am png")
378 upload = MediaIoBaseUpload(
379 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
380
381 http = HttpMockSequence([
382 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
383 ({'status': '200'}, '{}')
384 ])
385
386 model = JsonModel()
387 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
388 method = u'POST'
389 request = HttpRequest(
390 http,
391 model.response,
392 uri,
393 method=method,
394 headers={},
395 resumable=upload)
396
397 request._rand = lambda: 1.0
398 request._sleep = mock.MagicMock()
399
400 with self.assertRaises(HttpError):
401 request.execute(num_retries=3)
402 request._sleep.assert_not_called()
403
Joe Gregorio910b9b12012-06-12 09:36:30 -0400404
Joe Gregorio708388c2012-06-15 13:43:04 -0400405class TestMediaIoBaseDownload(unittest.TestCase):
406
407 def setUp(self):
408 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400409 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400410 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800411 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400412
413 def test_media_io_base_download(self):
414 self.request.http = HttpMockSequence([
415 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800416 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400417 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800418 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400419 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400420 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400421
422 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400423 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400424
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400425 self.assertEqual(self.fd, download._fd)
426 self.assertEqual(3, download._chunksize)
427 self.assertEqual(0, download._progress)
428 self.assertEqual(None, download._total_size)
429 self.assertEqual(False, download._done)
430 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400431
432 status, done = download.next_chunk()
433
Pat Ferate2b140222015-03-03 18:05:11 -0800434 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400435 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400436 self.assertEqual(3, download._progress)
437 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400438 self.assertEqual(3, status.resumable_progress)
439
440 status, done = download.next_chunk()
441
Pat Ferate2b140222015-03-03 18:05:11 -0800442 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400443 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400444 self.assertEqual(5, download._progress)
445 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400446
447 def test_media_io_base_download_handle_redirects(self):
448 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400449 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800450 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400451 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800452 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400453 ])
454
455 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400456 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400457
458 status, done = download.next_chunk()
459
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400460 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400461
462 def test_media_io_base_download_handle_4xx(self):
463 self.request.http = HttpMockSequence([
464 ({'status': '400'}, ''),
465 ])
466
467 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400468 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400469
470 try:
471 status, done = download.next_chunk()
472 self.fail('Should raise an exception')
473 except HttpError:
474 pass
475
476 # Even after raising an exception we can pick up where we left off.
477 self.request.http = HttpMockSequence([
478 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800479 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400480 ])
481
482 status, done = download.next_chunk()
483
Pat Ferate2b140222015-03-03 18:05:11 -0800484 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400485
eesheeshc6425a02016-02-12 15:07:06 +0000486 def test_media_io_base_download_retries_connection_errors(self):
487 self.request.http = HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100488 3, {'status': '200', 'content-range': '0-2/3'}, b'123')
489
490 download = MediaIoBaseDownload(
491 fd=self.fd, request=self.request, chunksize=3)
492 download._sleep = lambda _x: 0 # do nothing
493 download._rand = lambda: 10
494
495 status, done = download.next_chunk(num_retries=3)
496
497 self.assertEqual(self.fd.getvalue(), b'123')
498 self.assertEqual(True, done)
499
Joe Gregorio9086bd32013-06-14 16:32:05 -0400500 def test_media_io_base_download_retries_5xx(self):
501 self.request.http = HttpMockSequence([
502 ({'status': '500'}, ''),
503 ({'status': '500'}, ''),
504 ({'status': '500'}, ''),
505 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800506 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400507 ({'status': '503'}, ''),
508 ({'status': '503'}, ''),
509 ({'status': '503'}, ''),
510 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800511 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400512 ])
513
514 download = MediaIoBaseDownload(
515 fd=self.fd, request=self.request, chunksize=3)
516
517 self.assertEqual(self.fd, download._fd)
518 self.assertEqual(3, download._chunksize)
519 self.assertEqual(0, download._progress)
520 self.assertEqual(None, download._total_size)
521 self.assertEqual(False, download._done)
522 self.assertEqual(self.request.uri, download._uri)
523
524 # Set time.sleep and random.random stubs.
525 sleeptimes = []
526 download._sleep = lambda x: sleeptimes.append(x)
527 download._rand = lambda: 10
528
529 status, done = download.next_chunk(num_retries=3)
530
531 # Check for exponential backoff using the rand function above.
532 self.assertEqual([20, 40, 80], sleeptimes)
533
Pat Ferate2b140222015-03-03 18:05:11 -0800534 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400535 self.assertEqual(False, done)
536 self.assertEqual(3, download._progress)
537 self.assertEqual(5, download._total_size)
538 self.assertEqual(3, status.resumable_progress)
539
540 # Reset time.sleep stub.
541 del sleeptimes[0:len(sleeptimes)]
542
543 status, done = download.next_chunk(num_retries=3)
544
545 # Check for exponential backoff using the rand function above.
546 self.assertEqual([20, 40, 80], sleeptimes)
547
Pat Ferate2b140222015-03-03 18:05:11 -0800548 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400549 self.assertEqual(True, done)
550 self.assertEqual(5, download._progress)
551 self.assertEqual(5, download._total_size)
552
andrewnestera4a44cf2017-03-31 16:09:31 +0300553 def test_media_io_base_download_empty_file(self):
554 self.request.http = HttpMockSequence([
555 ({'status': '200',
556 'content-range': '0-0/0'}, b''),
557 ])
558
559 download = MediaIoBaseDownload(
560 fd=self.fd, request=self.request, chunksize=3)
561
562 self.assertEqual(self.fd, download._fd)
563 self.assertEqual(0, download._progress)
564 self.assertEqual(None, download._total_size)
565 self.assertEqual(False, download._done)
566 self.assertEqual(self.request.uri, download._uri)
567
568 status, done = download.next_chunk()
569
570 self.assertEqual(True, done)
571 self.assertEqual(0, download._progress)
572 self.assertEqual(0, download._total_size)
573 self.assertEqual(0, status.progress())
574
Joe Gregorio66f57522011-11-30 11:00:00 -0500575EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
576Content-Type: application/json
577MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500578Host: www.googleapis.com
579content-length: 2\r\n\r\n{}"""
580
581
582NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
583Content-Type: application/json
584MIME-Version: 1.0
585Host: www.googleapis.com
586content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500587
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400588NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
589Content-Type: application/json
590MIME-Version: 1.0
591Host: www.googleapis.com\r\n\r\n"""
592
Joe Gregorio66f57522011-11-30 11:00:00 -0500593
594RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400595Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500596Content-Length: 14
597ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
598
599
INADA Naoki09157612015-03-25 01:51:03 +0900600BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500601Content-Type: application/http
602Content-Transfer-Encoding: binary
603Content-ID: <randomness+1>
604
605HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400606Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500607Content-Length: 14
608ETag: "etag/pony"\r\n\r\n{"foo": 42}
609
610--batch_foobarbaz
611Content-Type: application/http
612Content-Transfer-Encoding: binary
613Content-ID: <randomness+2>
614
615HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400616Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500617Content-Length: 14
618ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
619--batch_foobarbaz--"""
620
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500621
INADA Naoki09157612015-03-25 01:51:03 +0900622BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400623Content-Type: application/http
624Content-Transfer-Encoding: binary
625Content-ID: <randomness+1>
626
627HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400628Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400629Content-Length: 14
630ETag: "etag/pony"\r\n\r\n{"foo": 42}
631
632--batch_foobarbaz
633Content-Type: application/http
634Content-Transfer-Encoding: binary
635Content-ID: <randomness+2>
636
637HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400638Content-Type: application/json
639Content-Length: 245
640ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400641 "error": {
642 "errors": [
643 {
644 "domain": "usageLimits",
645 "reason": "accessNotConfigured",
646 "message": "Access Not Configured",
647 "debugInfo": "QuotaState: BLOCKED"
648 }
649 ],
650 "code": 403,
651 "message": "Access Not Configured"
652 }
653}
654
655--batch_foobarbaz--"""
656
657
INADA Naoki09157612015-03-25 01:51:03 +0900658BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500659Content-Type: application/http
660Content-Transfer-Encoding: binary
661Content-ID: <randomness+1>
662
Joe Gregorioc752e332012-07-11 14:43:52 -0400663HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400664Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500665Content-Length: 14
666ETag: "etag/pony"\r\n\r\n{"error": {"message":
667 "Authorizaton failed."}}
668
669--batch_foobarbaz
670Content-Type: application/http
671Content-Transfer-Encoding: binary
672Content-ID: <randomness+2>
673
674HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400675Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500676Content-Length: 14
677ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
678--batch_foobarbaz--"""
679
680
INADA Naoki09157612015-03-25 01:51:03 +0900681BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500682Content-Type: application/http
683Content-Transfer-Encoding: binary
684Content-ID: <randomness+1>
685
686HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400687Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500688Content-Length: 14
689ETag: "etag/pony"\r\n\r\n{"foo": 42}
690--batch_foobarbaz--"""
691
eesheeshc6425a02016-02-12 15:07:06 +0000692
693USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
694 "error": {
695 "errors": [
696 {
697 "domain": "usageLimits",
698 "reason": "userRateLimitExceeded",
699 "message": "User Rate Limit Exceeded"
700 }
701 ],
702 "code": 403,
703 "message": "User Rate Limit Exceeded"
704 }
705}"""
706
707
708RATE_LIMIT_EXCEEDED_RESPONSE = """{
709 "error": {
710 "errors": [
711 {
712 "domain": "usageLimits",
713 "reason": "rateLimitExceeded",
714 "message": "Rate Limit Exceeded"
715 }
716 ],
717 "code": 403,
718 "message": "Rate Limit Exceeded"
719 }
720}"""
721
722
723NOT_CONFIGURED_RESPONSE = """{
724 "error": {
725 "errors": [
726 {
727 "domain": "usageLimits",
728 "reason": "accessNotConfigured",
729 "message": "Access Not Configured"
730 }
731 ],
732 "code": 403,
733 "message": "Access Not Configured"
734 }
735}"""
736
Joe Gregorio654f4a22012-02-09 14:15:44 -0500737class Callbacks(object):
738 def __init__(self):
739 self.responses = {}
740 self.exceptions = {}
741
742 def f(self, request_id, response, exception):
743 self.responses[request_id] = response
744 self.exceptions[request_id] = exception
745
746
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500747class TestHttpRequest(unittest.TestCase):
748 def test_unicode(self):
749 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
750 model = JsonModel()
751 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
752 method = u'POST'
753 request = HttpRequest(
754 http,
755 model.response,
756 uri,
757 method=method,
758 body=u'{}',
759 headers={'content-type': 'application/json'})
760 request.execute()
761 self.assertEqual(uri, http.uri)
762 self.assertEqual(str, type(http.uri))
763 self.assertEqual(method, http.method)
764 self.assertEqual(str, type(http.method))
765
eesheeshc6425a02016-02-12 15:07:06 +0000766 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100767 model = JsonModel()
768 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000769 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
770 model.response,
771 u'https://www.example.com/json_api_endpoint')
772 request._sleep = lambda _x: 0 # do nothing
773 request._rand = lambda: 10
774 with self.assertRaises(socket.error):
775 response = request.execute(num_retries=3)
776
777
778 def test_retry_connection_errors_non_resumable(self):
779 model = JsonModel()
780 request = HttpRequest(
781 HttpMockWithErrors(3, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100782 model.response,
783 u'https://www.example.com/json_api_endpoint')
784 request._sleep = lambda _x: 0 # do nothing
785 request._rand = lambda: 10
786 response = request.execute(num_retries=3)
787 self.assertEqual({u'foo': u'bar'}, response)
788
eesheeshc6425a02016-02-12 15:07:06 +0000789 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100790 with open(datafile('small.png'), 'rb') as small_png_file:
791 small_png_fd = BytesIO(small_png_file.read())
792 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
793 chunksize=500, resumable=True)
794 model = JsonModel()
795
796 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000797 HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100798 3, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
799 model.response,
800 u'https://www.example.com/file_upload',
801 method='POST',
802 resumable=upload)
803 request._sleep = lambda _x: 0 # do nothing
804 request._rand = lambda: 10
805 response = request.execute(num_retries=3)
806 self.assertEqual({u'foo': u'bar'}, response)
807
Joe Gregorio9086bd32013-06-14 16:32:05 -0400808 def test_retry(self):
809 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000810 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
811 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
812 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
813 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400814 resp_seq.append(({'status': '200'}, '{}'))
815
816 http = HttpMockSequence(resp_seq)
817 model = JsonModel()
818 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
819 method = u'POST'
820 request = HttpRequest(
821 http,
822 model.response,
823 uri,
824 method=method,
825 body=u'{}',
826 headers={'content-type': 'application/json'})
827
828 sleeptimes = []
829 request._sleep = lambda x: sleeptimes.append(x)
830 request._rand = lambda: 10
831
832 request.execute(num_retries=num_retries)
833
834 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900835 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400836 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
837
eesheeshc6425a02016-02-12 15:07:06 +0000838 def test_no_retry_succeeds(self):
839 num_retries = 5
840 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
841
842 http = HttpMockSequence(resp_seq)
843 model = JsonModel()
844 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
845 method = u'POST'
846 request = HttpRequest(
847 http,
848 model.response,
849 uri,
850 method=method,
851 body=u'{}',
852 headers={'content-type': 'application/json'})
853
854 sleeptimes = []
855 request._sleep = lambda x: sleeptimes.append(x)
856 request._rand = lambda: 10
857
858 request.execute(num_retries=num_retries)
859
860 self.assertEqual(0, len(sleeptimes))
861
Joe Gregorio9086bd32013-06-14 16:32:05 -0400862 def test_no_retry_fails_fast(self):
863 http = HttpMockSequence([
864 ({'status': '500'}, ''),
865 ({'status': '200'}, '{}')
866 ])
867 model = JsonModel()
868 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
869 method = u'POST'
870 request = HttpRequest(
871 http,
872 model.response,
873 uri,
874 method=method,
875 body=u'{}',
876 headers={'content-type': 'application/json'})
877
878 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000879 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400880
eesheeshc6425a02016-02-12 15:07:06 +0000881 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400882 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000883 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400884
eesheeshc6425a02016-02-12 15:07:06 +0000885 def test_no_retry_403_not_configured_fails_fast(self):
886 http = HttpMockSequence([
887 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
888 ({'status': '200'}, '{}')
889 ])
890 model = JsonModel()
891 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
892 method = u'POST'
893 request = HttpRequest(
894 http,
895 model.response,
896 uri,
897 method=method,
898 body=u'{}',
899 headers={'content-type': 'application/json'})
900
901 request._rand = lambda: 1.0
902 request._sleep = mock.MagicMock()
903
904 with self.assertRaises(HttpError):
905 request.execute()
906 request._sleep.assert_not_called()
907
908 def test_no_retry_403_fails_fast(self):
909 http = HttpMockSequence([
910 ({'status': '403'}, ''),
911 ({'status': '200'}, '{}')
912 ])
913 model = JsonModel()
914 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
915 method = u'POST'
916 request = HttpRequest(
917 http,
918 model.response,
919 uri,
920 method=method,
921 body=u'{}',
922 headers={'content-type': 'application/json'})
923
924 request._rand = lambda: 1.0
925 request._sleep = mock.MagicMock()
926
927 with self.assertRaises(HttpError):
928 request.execute()
929 request._sleep.assert_not_called()
930
931 def test_no_retry_401_fails_fast(self):
932 http = HttpMockSequence([
933 ({'status': '401'}, ''),
934 ({'status': '200'}, '{}')
935 ])
936 model = JsonModel()
937 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
938 method = u'POST'
939 request = HttpRequest(
940 http,
941 model.response,
942 uri,
943 method=method,
944 body=u'{}',
945 headers={'content-type': 'application/json'})
946
947 request._rand = lambda: 1.0
948 request._sleep = mock.MagicMock()
949
950 with self.assertRaises(HttpError):
951 request.execute()
952 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500953
Joe Gregorio66f57522011-11-30 11:00:00 -0500954class TestBatch(unittest.TestCase):
955
956 def setUp(self):
957 model = JsonModel()
958 self.request1 = HttpRequest(
959 None,
960 model.response,
961 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
962 method='POST',
963 body='{}',
964 headers={'content-type': 'application/json'})
965
966 self.request2 = HttpRequest(
967 None,
968 model.response,
969 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500970 method='GET',
971 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500972 headers={'content-type': 'application/json'})
973
974
975 def test_id_to_from_content_id_header(self):
976 batch = BatchHttpRequest()
977 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
978
979 def test_invalid_content_id_header(self):
980 batch = BatchHttpRequest()
981 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
982 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
983 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
984
985 def test_serialize_request(self):
986 batch = BatchHttpRequest()
987 request = HttpRequest(
988 None,
989 None,
990 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
991 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800992 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -0500993 headers={'content-type': 'application/json'},
994 methodId=None,
995 resumable=None)
996 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500997 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500998
Joe Gregoriodd813822012-01-25 10:32:47 -0500999 def test_serialize_request_media_body(self):
1000 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001001 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001002 body = f.read()
1003 f.close()
1004
1005 request = HttpRequest(
1006 None,
1007 None,
1008 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1009 method='POST',
1010 body=body,
1011 headers={'content-type': 'application/json'},
1012 methodId=None,
1013 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001014 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001015 s = batch._serialize_request(request).splitlines()
1016
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001017 def test_serialize_request_no_body(self):
1018 batch = BatchHttpRequest()
1019 request = HttpRequest(
1020 None,
1021 None,
1022 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1023 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001024 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001025 headers={'content-type': 'application/json'},
1026 methodId=None,
1027 resumable=None)
1028 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001029 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001030
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001031 def test_serialize_get_request_no_body(self):
1032 batch = BatchHttpRequest()
1033 request = HttpRequest(
1034 None,
1035 None,
1036 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1037 method='GET',
1038 body=None,
1039 headers={'content-type': 'application/json'},
1040 methodId=None,
1041 resumable=None)
1042 s = batch._serialize_request(request).splitlines()
1043 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1044
Joe Gregorio66f57522011-11-30 11:00:00 -05001045 def test_deserialize_response(self):
1046 batch = BatchHttpRequest()
1047 resp, content = batch._deserialize_response(RESPONSE)
1048
Joe Gregorio654f4a22012-02-09 14:15:44 -05001049 self.assertEqual(200, resp.status)
1050 self.assertEqual('OK', resp.reason)
1051 self.assertEqual(11, resp.version)
1052 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001053
1054 def test_new_id(self):
1055 batch = BatchHttpRequest()
1056
1057 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001058 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001059
1060 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001061 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001062
1063 batch.add(self.request1, request_id='3')
1064
1065 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001066 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001067
1068 def test_add(self):
1069 batch = BatchHttpRequest()
1070 batch.add(self.request1, request_id='1')
1071 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1072
1073 def test_add_fail_for_resumable(self):
1074 batch = BatchHttpRequest()
1075
1076 upload = MediaFileUpload(
1077 datafile('small.png'), chunksize=500, resumable=True)
1078 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001079 with self.assertRaises(BatchError) as batch_error:
1080 batch.add(self.request1, request_id='1')
1081 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001082
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001083 def test_execute_empty_batch_no_http(self):
1084 batch = BatchHttpRequest()
1085 ret = batch.execute()
1086 self.assertEqual(None, ret)
1087
Joe Gregorio66f57522011-11-30 11:00:00 -05001088 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001089 batch = BatchHttpRequest()
1090 callbacks = Callbacks()
1091
1092 batch.add(self.request1, callback=callbacks.f)
1093 batch.add(self.request2, callback=callbacks.f)
1094 http = HttpMockSequence([
1095 ({'status': '200',
1096 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1097 BATCH_RESPONSE),
1098 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001099 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001100 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1101 self.assertEqual(None, callbacks.exceptions['1'])
1102 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1103 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001104
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001105 def test_execute_request_body(self):
1106 batch = BatchHttpRequest()
1107
1108 batch.add(self.request1)
1109 batch.add(self.request2)
1110 http = HttpMockSequence([
1111 ({'status': '200',
1112 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1113 'echo_request_body'),
1114 ])
1115 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001116 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001117 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001118 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001119 boundary, _ = e.content.split(None, 1)
1120 self.assertEqual('--', boundary[:2])
1121 parts = e.content.split(boundary)
1122 self.assertEqual(4, len(parts))
1123 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001124 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001125 header = parts[1].splitlines()[1]
1126 self.assertEqual('Content-Type: application/http', header)
1127
Gabriel Garcia23174be2016-05-25 17:28:07 +02001128 def test_execute_initial_refresh_oauth2(self):
1129 batch = BatchHttpRequest()
1130 callbacks = Callbacks()
1131 cred = MockCredentials('Foo')
1132
1133 # Pretend this is a OAuth2Credentials object
1134 cred.access_token = None
1135
1136 http = HttpMockSequence([
1137 ({'status': '200',
1138 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1139 BATCH_SINGLE_RESPONSE),
1140 ])
1141
1142 cred.authorize(http)
1143
1144 batch.add(self.request1, callback=callbacks.f)
1145 batch.execute(http=http)
1146
1147 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1148 self.assertIsNone(callbacks.exceptions['1'])
1149
1150 self.assertEqual(1, cred._refreshed)
1151
1152 self.assertEqual(1, cred._authorized)
1153
1154 self.assertEqual(1, cred._applied)
1155
Joe Gregorio654f4a22012-02-09 14:15:44 -05001156 def test_execute_refresh_and_retry_on_401(self):
1157 batch = BatchHttpRequest()
1158 callbacks = Callbacks()
1159 cred_1 = MockCredentials('Foo')
1160 cred_2 = MockCredentials('Bar')
1161
1162 http = HttpMockSequence([
1163 ({'status': '200',
1164 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1165 BATCH_RESPONSE_WITH_401),
1166 ({'status': '200',
1167 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1168 BATCH_SINGLE_RESPONSE),
1169 ])
1170
1171 creds_http_1 = HttpMockSequence([])
1172 cred_1.authorize(creds_http_1)
1173
1174 creds_http_2 = HttpMockSequence([])
1175 cred_2.authorize(creds_http_2)
1176
1177 self.request1.http = creds_http_1
1178 self.request2.http = creds_http_2
1179
1180 batch.add(self.request1, callback=callbacks.f)
1181 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001182 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001183
1184 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1185 self.assertEqual(None, callbacks.exceptions['1'])
1186 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1187 self.assertEqual(None, callbacks.exceptions['2'])
1188
1189 self.assertEqual(1, cred_1._refreshed)
1190 self.assertEqual(0, cred_2._refreshed)
1191
1192 self.assertEqual(1, cred_1._authorized)
1193 self.assertEqual(1, cred_2._authorized)
1194
1195 self.assertEqual(1, cred_2._applied)
1196 self.assertEqual(2, cred_1._applied)
1197
1198 def test_http_errors_passed_to_callback(self):
1199 batch = BatchHttpRequest()
1200 callbacks = Callbacks()
1201 cred_1 = MockCredentials('Foo')
1202 cred_2 = MockCredentials('Bar')
1203
1204 http = HttpMockSequence([
1205 ({'status': '200',
1206 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1207 BATCH_RESPONSE_WITH_401),
1208 ({'status': '200',
1209 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1210 BATCH_RESPONSE_WITH_401),
1211 ])
1212
1213 creds_http_1 = HttpMockSequence([])
1214 cred_1.authorize(creds_http_1)
1215
1216 creds_http_2 = HttpMockSequence([])
1217 cred_2.authorize(creds_http_2)
1218
1219 self.request1.http = creds_http_1
1220 self.request2.http = creds_http_2
1221
1222 batch.add(self.request1, callback=callbacks.f)
1223 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001224 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001225
1226 self.assertEqual(None, callbacks.responses['1'])
1227 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001228 self.assertEqual(
1229 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001230 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1231 self.assertEqual(None, callbacks.exceptions['2'])
1232
Joe Gregorio66f57522011-11-30 11:00:00 -05001233 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001234 callbacks = Callbacks()
1235 batch = BatchHttpRequest(callback=callbacks.f)
1236
1237 batch.add(self.request1)
1238 batch.add(self.request2)
1239 http = HttpMockSequence([
1240 ({'status': '200',
1241 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1242 BATCH_RESPONSE),
1243 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001244 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001245 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1246 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001247
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001248 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001249 callbacks = Callbacks()
1250 batch = BatchHttpRequest(callback=callbacks.f)
1251
1252 batch.add(self.request1)
1253 batch.add(self.request2)
1254 http = HttpMockSequence([
1255 ({'status': '200',
1256 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1257 BATCH_ERROR_RESPONSE),
1258 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001259 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001260 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1261 expected = ('<HttpError 403 when requesting '
1262 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1263 '"Access Not Configured">')
1264 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001265
Joe Gregorio5c120db2012-08-23 09:13:55 -04001266
Joe Gregorioba5c7902012-08-03 12:48:16 -04001267class TestRequestUriTooLong(unittest.TestCase):
1268
1269 def test_turn_get_into_post(self):
1270
1271 def _postproc(resp, content):
1272 return content
1273
1274 http = HttpMockSequence([
1275 ({'status': '200'},
1276 'echo_request_body'),
1277 ({'status': '200'},
1278 'echo_request_headers'),
1279 ])
1280
1281 # Send a long query parameter.
1282 query = {
1283 'q': 'a' * MAX_URI_LENGTH + '?&'
1284 }
1285 req = HttpRequest(
1286 http,
1287 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001288 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001289 method='GET',
1290 body=None,
1291 headers={},
1292 methodId='foo',
1293 resumable=None)
1294
1295 # Query parameters should be sent in the body.
1296 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001297 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001298
1299 # Extra headers should be set.
1300 response = req.execute()
1301 self.assertEqual('GET', response['x-http-method-override'])
1302 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1303 self.assertEqual(
1304 'application/x-www-form-urlencoded', response['content-type'])
1305
Joe Gregorio5c120db2012-08-23 09:13:55 -04001306
1307class TestStreamSlice(unittest.TestCase):
1308 """Test _StreamSlice."""
1309
1310 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001311 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001312
1313 def test_read(self):
1314 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001315 self.assertEqual(b'', s.read(0))
1316 self.assertEqual(b'0', s.read(1))
1317 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001318
1319 def test_read_too_much(self):
1320 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001321 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001322
1323 def test_read_all(self):
1324 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001325 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001326
Ali Afshar164f37e2013-01-07 14:05:45 -08001327
1328class TestResponseCallback(unittest.TestCase):
1329 """Test adding callbacks to responses."""
1330
1331 def test_ensure_response_callback(self):
1332 m = JsonModel()
1333 request = HttpRequest(
1334 None,
1335 m.response,
1336 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1337 method='POST',
1338 body='{}',
1339 headers={'content-type': 'application/json'})
1340 h = HttpMockSequence([ ({'status': 200}, '{}')])
1341 responses = []
1342 def _on_response(resp, responses=responses):
1343 responses.append(resp)
1344 request.add_response_callback(_on_response)
1345 request.execute(http=h)
1346 self.assertEqual(1, len(responses))
1347
1348
Craig Gurnik8e55b762015-01-20 15:00:10 -05001349class TestHttpMock(unittest.TestCase):
1350 def test_default_response_headers(self):
1351 http = HttpMock(datafile('zoo.json'))
1352 resp, content = http.request("http://example.com")
1353 self.assertEqual(resp.status, 200)
1354
Alan Briolat26b01002015-08-14 00:13:57 +01001355 def test_error_response(self):
1356 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1357 model = JsonModel()
1358 request = HttpRequest(
1359 http,
1360 model.response,
1361 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1362 method='GET',
1363 headers={})
1364 self.assertRaises(HttpError, request.execute)
1365
Craig Gurnik8e55b762015-01-20 15:00:10 -05001366
Igor Maravić22435292017-01-19 22:28:22 +01001367class TestHttpBuild(unittest.TestCase):
1368 original_socket_default_timeout = None
1369
1370 @classmethod
1371 def setUpClass(cls):
1372 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1373
1374 @classmethod
1375 def tearDownClass(cls):
1376 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1377
1378 def test_build_http_sets_default_timeout_if_none_specified(self):
1379 socket.setdefaulttimeout(None)
1380 http = build_http()
1381 self.assertIsInstance(http.timeout, int)
1382 self.assertGreater(http.timeout, 0)
1383
1384 def test_build_http_default_timeout_can_be_overridden(self):
1385 socket.setdefaulttimeout(1.5)
1386 http = build_http()
1387 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1388
1389 def test_build_http_default_timeout_can_be_set_to_zero(self):
1390 socket.setdefaulttimeout(0)
1391 http = build_http()
1392 self.assertEquals(http.timeout, 0)
1393
1394
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001395if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001396 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001397 unittest.main()