blob: 47f9c57e8c3ad9836d53f32f396292f3b290a9aa [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."""
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070065 def __init__(self, bearer_token, expired=False):
Joe Gregorio654f4a22012-02-09 14:15:44 -050066 super(MockCredentials, self).__init__()
67 self._authorized = 0
68 self._refreshed = 0
69 self._applied = 0
70 self._bearer_token = bearer_token
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070071 self._access_token_expired = expired
72
73 @property
Jon Wayne Parrott20e61352018-01-18 09:16:37 -080074 def access_token(self):
75 return self._bearer_token
76
77 @property
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070078 def access_token_expired(self):
79 return self._access_token_expired
Joe Gregorio654f4a22012-02-09 14:15:44 -050080
81 def authorize(self, http):
82 self._authorized += 1
83
84 request_orig = http.request
85
86 # The closure that will replace 'httplib2.Http.request'.
87 def new_request(uri, method='GET', body=None, headers=None,
88 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
89 connection_type=None):
90 # Modify the request headers to add the appropriate
91 # Authorization header.
92 if headers is None:
93 headers = {}
94 self.apply(headers)
95
96 resp, content = request_orig(uri, method, body, headers,
97 redirections, connection_type)
98
99 return resp, content
100
101 # Replace the request method with our own closure.
102 http.request = new_request
103
104 # Set credentials as a property of the request method.
105 setattr(http.request, 'credentials', self)
106
107 return http
108
109 def refresh(self, http):
110 self._refreshed += 1
111
112 def apply(self, headers):
113 self._applied += 1
114 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500115
116
eesheeshc6425a02016-02-12 15:07:06 +0000117class HttpMockWithErrors(object):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100118 def __init__(self, num_errors, success_json, success_data):
119 self.num_errors = num_errors
120 self.success_json = success_json
121 self.success_data = success_data
122
123 def request(self, *args, **kwargs):
124 if not self.num_errors:
125 return httplib2.Response(self.success_json), self.success_data
126 else:
127 self.num_errors -= 1
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700128 if self.num_errors == 1: # initial == 2
eesheeshc6425a02016-02-12 15:07:06 +0000129 raise ssl.SSLError()
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400130 if self.num_errors == 3: # initial == 4
131 raise httplib2.ServerNotFoundError()
132 else: # initial != 2,4
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200133 if self.num_errors == 2:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700134 # first try a broken pipe error (#218)
135 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200136 ex.errno = socket.errno.EPIPE
137 else:
138 # Initialize the timeout error code to the platform's error code.
139 try:
140 # For Windows:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700141 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200142 ex.errno = socket.errno.WSAETIMEDOUT
143 except AttributeError:
144 # For Linux/Mac:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700145 if PY3:
146 ex = socket.timeout()
147 else:
148 ex = socket.error()
149 ex.errno = socket.errno.ETIMEDOUT
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200150 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000151 raise ex
152
153
154class HttpMockWithNonRetriableErrors(object):
155 def __init__(self, num_errors, success_json, success_data):
156 self.num_errors = num_errors
157 self.success_json = success_json
158 self.success_data = success_data
159
160 def request(self, *args, **kwargs):
161 if not self.num_errors:
162 return httplib2.Response(self.success_json), self.success_data
163 else:
164 self.num_errors -= 1
165 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200166 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000167 try:
168 # For Windows:
169 ex.errno = socket.errno.WSAECONNREFUSED
170 except AttributeError:
171 # For Linux/Mac:
172 ex.errno = socket.errno.ECONNREFUSED
173 # Now raise the correct timeout error.
174 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100175
176
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500177DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
178
179
180def datafile(filename):
181 return os.path.join(DATA_DIR, filename)
182
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100183def _postproc_none(*kwargs):
184 pass
185
186
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500187class TestUserAgent(unittest.TestCase):
188
189 def test_set_user_agent(self):
190 http = HttpMockSequence([
191 ({'status': '200'}, 'echo_request_headers'),
192 ])
193
194 http = set_user_agent(http, "my_app/5.5")
195 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500196 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500197
198 def test_set_user_agent_nested(self):
199 http = HttpMockSequence([
200 ({'status': '200'}, 'echo_request_headers'),
201 ])
202
203 http = set_user_agent(http, "my_app/5.5")
204 http = set_user_agent(http, "my_library/0.1")
205 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500206 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500207
Joe Gregorio910b9b12012-06-12 09:36:30 -0400208
209class TestMediaUpload(unittest.TestCase):
210
Nam T. Nguyendc136312015-12-01 10:18:56 -0800211 def test_media_file_upload_mimetype_detection(self):
212 upload = MediaFileUpload(datafile('small.png'))
213 self.assertEqual('image/png', upload.mimetype())
214
215 upload = MediaFileUpload(datafile('empty'))
216 self.assertEqual('application/octet-stream', upload.mimetype())
217
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500218 def test_media_file_upload_to_from_json(self):
219 upload = MediaFileUpload(
220 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500221 self.assertEqual('image/png', upload.mimetype())
222 self.assertEqual(190, upload.size())
223 self.assertEqual(True, upload.resumable())
224 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800225 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500226
227 json = upload.to_json()
228 new_upload = MediaUpload.new_from_json(json)
229
Joe Gregorio654f4a22012-02-09 14:15:44 -0500230 self.assertEqual('image/png', new_upload.mimetype())
231 self.assertEqual(190, new_upload.size())
232 self.assertEqual(True, new_upload.resumable())
233 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800234 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500235
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400236 def test_media_file_upload_raises_on_invalid_chunksize(self):
237 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
238 datafile('small.png'), mimetype='image/png', chunksize=-2,
239 resumable=True)
240
Ali Afshar1cb6b672012-03-12 08:46:14 -0400241 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800242 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400243 resumable=True)
244 self.assertEqual('text/plain', media.mimetype())
245 self.assertEqual(10, media.chunksize())
246 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800247 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400248 self.assertEqual(6, media.size())
249
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500250 def test_http_request_to_from_json(self):
Igor Maravić22435292017-01-19 22:28:22 +0100251 http = build_http()
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500252 media_upload = MediaFileUpload(
253 datafile('small.png'), chunksize=500, resumable=True)
254 req = HttpRequest(
255 http,
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100256 _postproc_none,
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500257 'http://example.com',
258 method='POST',
259 body='{}',
260 headers={'content-type': 'multipart/related; boundary="---flubber"'},
261 methodId='foo',
262 resumable=media_upload)
263
264 json = req.to_json()
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100265 new_req = HttpRequest.from_json(json, http, _postproc_none)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500266
Joe Gregorio654f4a22012-02-09 14:15:44 -0500267 self.assertEqual({'content-type':
268 'multipart/related; boundary="---flubber"'},
269 new_req.headers)
270 self.assertEqual('http://example.com', new_req.uri)
271 self.assertEqual('{}', new_req.body)
272 self.assertEqual(http, new_req.http)
273 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500274
Joe Gregorio9086bd32013-06-14 16:32:05 -0400275 self.assertEqual(random.random, new_req._rand)
276 self.assertEqual(time.sleep, new_req._sleep)
277
Joe Gregorio910b9b12012-06-12 09:36:30 -0400278
279class TestMediaIoBaseUpload(unittest.TestCase):
280
281 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800282 fd = FileIO(datafile('small.png'), 'r')
283 upload = MediaIoBaseUpload(
284 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
285 self.assertEqual('image/png', upload.mimetype())
286 self.assertEqual(190, upload.size())
287 self.assertEqual(True, upload.resumable())
288 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800289 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400290
291 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800292 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400293 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400294 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400295 self.assertEqual('image/png', upload.mimetype())
296 self.assertEqual(190, upload.size())
297 self.assertEqual(True, upload.resumable())
298 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800299 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400300 f.close()
301
302 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800303 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400304 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400305
306 try:
307 json = upload.to_json()
308 self.fail('MediaIoBaseUpload should not be serializable.')
309 except NotImplementedError:
310 pass
311
Pat Feratec6050872015-03-03 18:24:59 -0800312 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400313 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800314 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800315 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400316 f.close()
317
318 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400319 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400320 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400321 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400322 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 f.close()
326
327 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800328 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800329 fd = BytesIO(f.read())
330 upload = MediaIoBaseUpload(
331 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
332 self.assertEqual('image/png', upload.mimetype())
333 self.assertEqual(190, upload.size())
334 self.assertEqual(True, upload.resumable())
335 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800336 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400337
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400338 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800339 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800340 fd = BytesIO(f.read())
341 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
342 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400343
344 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800345 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800346 upload = MediaIoBaseUpload(
347 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
348 self.assertEqual(True, upload.has_stream())
349 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400350
Joe Gregorio9086bd32013-06-14 16:32:05 -0400351 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800352 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800353 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400354 upload = MediaIoBaseUpload(
355 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
356
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500357 # Simulate errors for both the request that creates the resumable upload
358 # and the upload itself.
Joe Gregorio9086bd32013-06-14 16:32:05 -0400359 http = HttpMockSequence([
360 ({'status': '500'}, ''),
361 ({'status': '500'}, ''),
362 ({'status': '503'}, ''),
363 ({'status': '200', 'location': 'location'}, ''),
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500364 ({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
365 ({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE),
366 ({'status': '429'}, ''),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400367 ({'status': '200'}, '{}'),
368 ])
369
370 model = JsonModel()
371 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
372 method = u'POST'
373 request = HttpRequest(
374 http,
375 model.response,
376 uri,
377 method=method,
378 headers={},
379 resumable=upload)
380
381 sleeptimes = []
382 request._sleep = lambda x: sleeptimes.append(x)
383 request._rand = lambda: 10
384
385 request.execute(num_retries=3)
386 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
387
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500388 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
389 fd = BytesIO(b"i am png")
390 upload = MediaIoBaseUpload(
391 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
392
393 http = HttpMockSequence([
394 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
395 ({'status': '200'}, '{}')
396 ])
397
398 model = JsonModel()
399 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
400 method = u'POST'
401 request = HttpRequest(
402 http,
403 model.response,
404 uri,
405 method=method,
406 headers={},
407 resumable=upload)
408
409 request._rand = lambda: 1.0
410 request._sleep = mock.MagicMock()
411
412 with self.assertRaises(HttpError):
413 request.execute(num_retries=3)
414 request._sleep.assert_not_called()
415
Joe Gregorio910b9b12012-06-12 09:36:30 -0400416
Joe Gregorio708388c2012-06-15 13:43:04 -0400417class TestMediaIoBaseDownload(unittest.TestCase):
418
419 def setUp(self):
420 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400421 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400422 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800423 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400424
425 def test_media_io_base_download(self):
426 self.request.http = HttpMockSequence([
427 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800428 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400429 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800430 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400431 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400432 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400433
434 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400435 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400436
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400437 self.assertEqual(self.fd, download._fd)
438 self.assertEqual(3, download._chunksize)
439 self.assertEqual(0, download._progress)
440 self.assertEqual(None, download._total_size)
441 self.assertEqual(False, download._done)
442 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400443
444 status, done = download.next_chunk()
445
Pat Ferate2b140222015-03-03 18:05:11 -0800446 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400447 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400448 self.assertEqual(3, download._progress)
449 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400450 self.assertEqual(3, status.resumable_progress)
451
452 status, done = download.next_chunk()
453
Pat Ferate2b140222015-03-03 18:05:11 -0800454 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400455 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400456 self.assertEqual(5, download._progress)
457 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400458
459 def test_media_io_base_download_handle_redirects(self):
460 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400461 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800462 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400463 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800464 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400465 ])
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 status, done = download.next_chunk()
471
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400472 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400473
474 def test_media_io_base_download_handle_4xx(self):
475 self.request.http = HttpMockSequence([
476 ({'status': '400'}, ''),
477 ])
478
479 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400480 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400481
482 try:
483 status, done = download.next_chunk()
484 self.fail('Should raise an exception')
485 except HttpError:
486 pass
487
488 # Even after raising an exception we can pick up where we left off.
489 self.request.http = HttpMockSequence([
490 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800491 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400492 ])
493
494 status, done = download.next_chunk()
495
Pat Ferate2b140222015-03-03 18:05:11 -0800496 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400497
eesheeshc6425a02016-02-12 15:07:06 +0000498 def test_media_io_base_download_retries_connection_errors(self):
499 self.request.http = HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400500 4, {'status': '200', 'content-range': '0-2/3'}, b'123')
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100501
502 download = MediaIoBaseDownload(
503 fd=self.fd, request=self.request, chunksize=3)
504 download._sleep = lambda _x: 0 # do nothing
505 download._rand = lambda: 10
506
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400507 status, done = download.next_chunk(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100508
509 self.assertEqual(self.fd.getvalue(), b'123')
510 self.assertEqual(True, done)
511
Joe Gregorio9086bd32013-06-14 16:32:05 -0400512 def test_media_io_base_download_retries_5xx(self):
513 self.request.http = HttpMockSequence([
514 ({'status': '500'}, ''),
515 ({'status': '500'}, ''),
516 ({'status': '500'}, ''),
517 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800518 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400519 ({'status': '503'}, ''),
520 ({'status': '503'}, ''),
521 ({'status': '503'}, ''),
522 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800523 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400524 ])
525
526 download = MediaIoBaseDownload(
527 fd=self.fd, request=self.request, chunksize=3)
528
529 self.assertEqual(self.fd, download._fd)
530 self.assertEqual(3, download._chunksize)
531 self.assertEqual(0, download._progress)
532 self.assertEqual(None, download._total_size)
533 self.assertEqual(False, download._done)
534 self.assertEqual(self.request.uri, download._uri)
535
536 # Set time.sleep and random.random stubs.
537 sleeptimes = []
538 download._sleep = lambda x: sleeptimes.append(x)
539 download._rand = lambda: 10
540
541 status, done = download.next_chunk(num_retries=3)
542
543 # Check for exponential backoff using the rand function above.
544 self.assertEqual([20, 40, 80], sleeptimes)
545
Pat Ferate2b140222015-03-03 18:05:11 -0800546 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400547 self.assertEqual(False, done)
548 self.assertEqual(3, download._progress)
549 self.assertEqual(5, download._total_size)
550 self.assertEqual(3, status.resumable_progress)
551
552 # Reset time.sleep stub.
553 del sleeptimes[0:len(sleeptimes)]
554
555 status, done = download.next_chunk(num_retries=3)
556
557 # Check for exponential backoff using the rand function above.
558 self.assertEqual([20, 40, 80], sleeptimes)
559
Pat Ferate2b140222015-03-03 18:05:11 -0800560 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400561 self.assertEqual(True, done)
562 self.assertEqual(5, download._progress)
563 self.assertEqual(5, download._total_size)
564
andrewnestera4a44cf2017-03-31 16:09:31 +0300565 def test_media_io_base_download_empty_file(self):
566 self.request.http = HttpMockSequence([
567 ({'status': '200',
568 'content-range': '0-0/0'}, b''),
569 ])
570
571 download = MediaIoBaseDownload(
572 fd=self.fd, request=self.request, chunksize=3)
573
574 self.assertEqual(self.fd, download._fd)
575 self.assertEqual(0, download._progress)
576 self.assertEqual(None, download._total_size)
577 self.assertEqual(False, download._done)
578 self.assertEqual(self.request.uri, download._uri)
579
580 status, done = download.next_chunk()
581
582 self.assertEqual(True, done)
583 self.assertEqual(0, download._progress)
584 self.assertEqual(0, download._total_size)
585 self.assertEqual(0, status.progress())
586
Daniel44067782018-01-16 23:17:56 +0100587 def test_media_io_base_download_unknown_media_size(self):
588 self.request.http = HttpMockSequence([
589 ({'status': '200'}, b'123')
590 ])
591
592 download = MediaIoBaseDownload(
593 fd=self.fd, request=self.request, chunksize=3)
594
595 self.assertEqual(self.fd, download._fd)
596 self.assertEqual(0, download._progress)
597 self.assertEqual(None, download._total_size)
598 self.assertEqual(False, download._done)
599 self.assertEqual(self.request.uri, download._uri)
600
601 status, done = download.next_chunk()
602
603 self.assertEqual(self.fd.getvalue(), b'123')
604 self.assertEqual(True, done)
605 self.assertEqual(3, download._progress)
606 self.assertEqual(None, download._total_size)
607 self.assertEqual(0, status.progress())
608
609
Joe Gregorio66f57522011-11-30 11:00:00 -0500610EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
611Content-Type: application/json
612MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500613Host: www.googleapis.com
614content-length: 2\r\n\r\n{}"""
615
616
617NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
618Content-Type: application/json
619MIME-Version: 1.0
620Host: www.googleapis.com
621content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500622
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400623NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
624Content-Type: application/json
625MIME-Version: 1.0
626Host: www.googleapis.com\r\n\r\n"""
627
Joe Gregorio66f57522011-11-30 11:00:00 -0500628
629RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400630Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500631Content-Length: 14
632ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
633
634
INADA Naoki09157612015-03-25 01:51:03 +0900635BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500636Content-Type: application/http
637Content-Transfer-Encoding: binary
638Content-ID: <randomness+1>
639
640HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400641Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500642Content-Length: 14
643ETag: "etag/pony"\r\n\r\n{"foo": 42}
644
645--batch_foobarbaz
646Content-Type: application/http
647Content-Transfer-Encoding: binary
648Content-ID: <randomness+2>
649
650HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400651Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500652Content-Length: 14
653ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
654--batch_foobarbaz--"""
655
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500656
INADA Naoki09157612015-03-25 01:51:03 +0900657BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400658Content-Type: application/http
659Content-Transfer-Encoding: binary
660Content-ID: <randomness+1>
661
662HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400663Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400664Content-Length: 14
665ETag: "etag/pony"\r\n\r\n{"foo": 42}
666
667--batch_foobarbaz
668Content-Type: application/http
669Content-Transfer-Encoding: binary
670Content-ID: <randomness+2>
671
672HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400673Content-Type: application/json
674Content-Length: 245
675ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400676 "error": {
677 "errors": [
678 {
679 "domain": "usageLimits",
680 "reason": "accessNotConfigured",
681 "message": "Access Not Configured",
682 "debugInfo": "QuotaState: BLOCKED"
683 }
684 ],
685 "code": 403,
686 "message": "Access Not Configured"
687 }
688}
689
690--batch_foobarbaz--"""
691
692
INADA Naoki09157612015-03-25 01:51:03 +0900693BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500694Content-Type: application/http
695Content-Transfer-Encoding: binary
696Content-ID: <randomness+1>
697
Joe Gregorioc752e332012-07-11 14:43:52 -0400698HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400699Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500700Content-Length: 14
701ETag: "etag/pony"\r\n\r\n{"error": {"message":
702 "Authorizaton failed."}}
703
704--batch_foobarbaz
705Content-Type: application/http
706Content-Transfer-Encoding: binary
707Content-ID: <randomness+2>
708
709HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400710Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500711Content-Length: 14
712ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
713--batch_foobarbaz--"""
714
715
INADA Naoki09157612015-03-25 01:51:03 +0900716BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500717Content-Type: application/http
718Content-Transfer-Encoding: binary
719Content-ID: <randomness+1>
720
721HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400722Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500723Content-Length: 14
724ETag: "etag/pony"\r\n\r\n{"foo": 42}
725--batch_foobarbaz--"""
726
eesheeshc6425a02016-02-12 15:07:06 +0000727
728USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
729 "error": {
730 "errors": [
731 {
732 "domain": "usageLimits",
733 "reason": "userRateLimitExceeded",
734 "message": "User Rate Limit Exceeded"
735 }
736 ],
737 "code": 403,
738 "message": "User Rate Limit Exceeded"
739 }
740}"""
741
742
743RATE_LIMIT_EXCEEDED_RESPONSE = """{
744 "error": {
745 "errors": [
746 {
747 "domain": "usageLimits",
748 "reason": "rateLimitExceeded",
749 "message": "Rate Limit Exceeded"
750 }
751 ],
752 "code": 403,
753 "message": "Rate Limit Exceeded"
754 }
755}"""
756
757
758NOT_CONFIGURED_RESPONSE = """{
759 "error": {
760 "errors": [
761 {
762 "domain": "usageLimits",
763 "reason": "accessNotConfigured",
764 "message": "Access Not Configured"
765 }
766 ],
767 "code": 403,
768 "message": "Access Not Configured"
769 }
770}"""
771
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800772LIST_NOT_CONFIGURED_RESPONSE = """[
773 "error": {
774 "errors": [
775 {
776 "domain": "usageLimits",
777 "reason": "accessNotConfigured",
778 "message": "Access Not Configured"
779 }
780 ],
781 "code": 403,
782 "message": "Access Not Configured"
783 }
784]"""
785
Joe Gregorio654f4a22012-02-09 14:15:44 -0500786class Callbacks(object):
787 def __init__(self):
788 self.responses = {}
789 self.exceptions = {}
790
791 def f(self, request_id, response, exception):
792 self.responses[request_id] = response
793 self.exceptions[request_id] = exception
794
795
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500796class TestHttpRequest(unittest.TestCase):
797 def test_unicode(self):
798 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
799 model = JsonModel()
800 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
801 method = u'POST'
802 request = HttpRequest(
803 http,
804 model.response,
805 uri,
806 method=method,
807 body=u'{}',
808 headers={'content-type': 'application/json'})
809 request.execute()
810 self.assertEqual(uri, http.uri)
811 self.assertEqual(str, type(http.uri))
812 self.assertEqual(method, http.method)
813 self.assertEqual(str, type(http.method))
814
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100815 def test_empty_content_type(self):
816 """Test for #284"""
817 http = HttpMock(None, headers={'status': 200})
818 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
819 method = u'POST'
820 request = HttpRequest(
821 http,
822 _postproc_none,
823 uri,
824 method=method,
825 headers={'content-type': ''})
826 request.execute()
827 self.assertEqual('', http.headers.get('content-type'))
828
eesheeshc6425a02016-02-12 15:07:06 +0000829 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100830 model = JsonModel()
831 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000832 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
833 model.response,
834 u'https://www.example.com/json_api_endpoint')
835 request._sleep = lambda _x: 0 # do nothing
836 request._rand = lambda: 10
837 with self.assertRaises(socket.error):
838 response = request.execute(num_retries=3)
839
840
841 def test_retry_connection_errors_non_resumable(self):
842 model = JsonModel()
843 request = HttpRequest(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400844 HttpMockWithErrors(4, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100845 model.response,
846 u'https://www.example.com/json_api_endpoint')
847 request._sleep = lambda _x: 0 # do nothing
848 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400849 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100850 self.assertEqual({u'foo': u'bar'}, response)
851
eesheeshc6425a02016-02-12 15:07:06 +0000852 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100853 with open(datafile('small.png'), 'rb') as small_png_file:
854 small_png_fd = BytesIO(small_png_file.read())
855 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
856 chunksize=500, resumable=True)
857 model = JsonModel()
858
859 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000860 HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400861 4, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100862 model.response,
863 u'https://www.example.com/file_upload',
864 method='POST',
865 resumable=upload)
866 request._sleep = lambda _x: 0 # do nothing
867 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400868 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100869 self.assertEqual({u'foo': u'bar'}, response)
870
Joe Gregorio9086bd32013-06-14 16:32:05 -0400871 def test_retry(self):
872 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000873 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
874 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
875 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
876 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400877 resp_seq.append(({'status': '200'}, '{}'))
878
879 http = HttpMockSequence(resp_seq)
880 model = JsonModel()
881 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
882 method = u'POST'
883 request = HttpRequest(
884 http,
885 model.response,
886 uri,
887 method=method,
888 body=u'{}',
889 headers={'content-type': 'application/json'})
890
891 sleeptimes = []
892 request._sleep = lambda x: sleeptimes.append(x)
893 request._rand = lambda: 10
894
895 request.execute(num_retries=num_retries)
896
897 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900898 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400899 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
900
eesheeshc6425a02016-02-12 15:07:06 +0000901 def test_no_retry_succeeds(self):
902 num_retries = 5
903 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
904
905 http = HttpMockSequence(resp_seq)
906 model = JsonModel()
907 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
908 method = u'POST'
909 request = HttpRequest(
910 http,
911 model.response,
912 uri,
913 method=method,
914 body=u'{}',
915 headers={'content-type': 'application/json'})
916
917 sleeptimes = []
918 request._sleep = lambda x: sleeptimes.append(x)
919 request._rand = lambda: 10
920
921 request.execute(num_retries=num_retries)
922
923 self.assertEqual(0, len(sleeptimes))
924
Joe Gregorio9086bd32013-06-14 16:32:05 -0400925 def test_no_retry_fails_fast(self):
926 http = HttpMockSequence([
927 ({'status': '500'}, ''),
928 ({'status': '200'}, '{}')
929 ])
930 model = JsonModel()
931 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
932 method = u'POST'
933 request = HttpRequest(
934 http,
935 model.response,
936 uri,
937 method=method,
938 body=u'{}',
939 headers={'content-type': 'application/json'})
940
941 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000942 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400943
eesheeshc6425a02016-02-12 15:07:06 +0000944 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400945 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000946 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400947
eesheeshc6425a02016-02-12 15:07:06 +0000948 def test_no_retry_403_not_configured_fails_fast(self):
949 http = HttpMockSequence([
950 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
951 ({'status': '200'}, '{}')
952 ])
953 model = JsonModel()
954 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
955 method = u'POST'
956 request = HttpRequest(
957 http,
958 model.response,
959 uri,
960 method=method,
961 body=u'{}',
962 headers={'content-type': 'application/json'})
963
964 request._rand = lambda: 1.0
965 request._sleep = mock.MagicMock()
966
967 with self.assertRaises(HttpError):
968 request.execute()
969 request._sleep.assert_not_called()
970
971 def test_no_retry_403_fails_fast(self):
972 http = HttpMockSequence([
973 ({'status': '403'}, ''),
974 ({'status': '200'}, '{}')
975 ])
976 model = JsonModel()
977 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
978 method = u'POST'
979 request = HttpRequest(
980 http,
981 model.response,
982 uri,
983 method=method,
984 body=u'{}',
985 headers={'content-type': 'application/json'})
986
987 request._rand = lambda: 1.0
988 request._sleep = mock.MagicMock()
989
990 with self.assertRaises(HttpError):
991 request.execute()
992 request._sleep.assert_not_called()
993
994 def test_no_retry_401_fails_fast(self):
995 http = HttpMockSequence([
996 ({'status': '401'}, ''),
997 ({'status': '200'}, '{}')
998 ])
999 model = JsonModel()
1000 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1001 method = u'POST'
1002 request = HttpRequest(
1003 http,
1004 model.response,
1005 uri,
1006 method=method,
1007 body=u'{}',
1008 headers={'content-type': 'application/json'})
1009
1010 request._rand = lambda: 1.0
1011 request._sleep = mock.MagicMock()
1012
1013 with self.assertRaises(HttpError):
1014 request.execute()
1015 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001016
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001017 def test_no_retry_403_list_fails(self):
1018 http = HttpMockSequence([
1019 ({'status': '403'}, LIST_NOT_CONFIGURED_RESPONSE),
1020 ({'status': '200'}, '{}')
1021 ])
1022 model = JsonModel()
1023 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1024 method = u'POST'
1025 request = HttpRequest(
1026 http,
1027 model.response,
1028 uri,
1029 method=method,
1030 body=u'{}',
1031 headers={'content-type': 'application/json'})
1032
1033 request._rand = lambda: 1.0
1034 request._sleep = mock.MagicMock()
1035
1036 with self.assertRaises(HttpError):
1037 request.execute()
1038 request._sleep.assert_not_called()
1039
Joe Gregorio66f57522011-11-30 11:00:00 -05001040class TestBatch(unittest.TestCase):
1041
1042 def setUp(self):
1043 model = JsonModel()
1044 self.request1 = HttpRequest(
1045 None,
1046 model.response,
1047 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1048 method='POST',
1049 body='{}',
1050 headers={'content-type': 'application/json'})
1051
1052 self.request2 = HttpRequest(
1053 None,
1054 model.response,
1055 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001056 method='GET',
1057 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -05001058 headers={'content-type': 'application/json'})
1059
1060
1061 def test_id_to_from_content_id_header(self):
1062 batch = BatchHttpRequest()
1063 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
1064
1065 def test_invalid_content_id_header(self):
1066 batch = BatchHttpRequest()
1067 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
1068 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
1069 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
1070
1071 def test_serialize_request(self):
1072 batch = BatchHttpRequest()
1073 request = HttpRequest(
1074 None,
1075 None,
1076 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1077 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001078 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -05001079 headers={'content-type': 'application/json'},
1080 methodId=None,
1081 resumable=None)
1082 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001083 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001084
Joe Gregoriodd813822012-01-25 10:32:47 -05001085 def test_serialize_request_media_body(self):
1086 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001087 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001088 body = f.read()
1089 f.close()
1090
1091 request = HttpRequest(
1092 None,
1093 None,
1094 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1095 method='POST',
1096 body=body,
1097 headers={'content-type': 'application/json'},
1098 methodId=None,
1099 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001100 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001101 s = batch._serialize_request(request).splitlines()
1102
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001103 def test_serialize_request_no_body(self):
1104 batch = BatchHttpRequest()
1105 request = HttpRequest(
1106 None,
1107 None,
1108 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1109 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001110 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001111 headers={'content-type': 'application/json'},
1112 methodId=None,
1113 resumable=None)
1114 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001115 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001116
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001117 def test_serialize_get_request_no_body(self):
1118 batch = BatchHttpRequest()
1119 request = HttpRequest(
1120 None,
1121 None,
1122 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1123 method='GET',
1124 body=None,
1125 headers={'content-type': 'application/json'},
1126 methodId=None,
1127 resumable=None)
1128 s = batch._serialize_request(request).splitlines()
1129 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1130
Joe Gregorio66f57522011-11-30 11:00:00 -05001131 def test_deserialize_response(self):
1132 batch = BatchHttpRequest()
1133 resp, content = batch._deserialize_response(RESPONSE)
1134
Joe Gregorio654f4a22012-02-09 14:15:44 -05001135 self.assertEqual(200, resp.status)
1136 self.assertEqual('OK', resp.reason)
1137 self.assertEqual(11, resp.version)
1138 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001139
1140 def test_new_id(self):
1141 batch = BatchHttpRequest()
1142
1143 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001144 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001145
1146 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001147 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001148
1149 batch.add(self.request1, request_id='3')
1150
1151 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001152 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001153
1154 def test_add(self):
1155 batch = BatchHttpRequest()
1156 batch.add(self.request1, request_id='1')
1157 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1158
1159 def test_add_fail_for_resumable(self):
1160 batch = BatchHttpRequest()
1161
1162 upload = MediaFileUpload(
1163 datafile('small.png'), chunksize=500, resumable=True)
1164 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001165 with self.assertRaises(BatchError) as batch_error:
1166 batch.add(self.request1, request_id='1')
1167 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001168
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001169 def test_execute_empty_batch_no_http(self):
1170 batch = BatchHttpRequest()
1171 ret = batch.execute()
1172 self.assertEqual(None, ret)
1173
Joe Gregorio66f57522011-11-30 11:00:00 -05001174 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001175 batch = BatchHttpRequest()
1176 callbacks = Callbacks()
1177
1178 batch.add(self.request1, callback=callbacks.f)
1179 batch.add(self.request2, callback=callbacks.f)
1180 http = HttpMockSequence([
1181 ({'status': '200',
1182 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1183 BATCH_RESPONSE),
1184 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001185 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001186 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1187 self.assertEqual(None, callbacks.exceptions['1'])
1188 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1189 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001190
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001191 def test_execute_request_body(self):
1192 batch = BatchHttpRequest()
1193
1194 batch.add(self.request1)
1195 batch.add(self.request2)
1196 http = HttpMockSequence([
1197 ({'status': '200',
1198 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1199 'echo_request_body'),
1200 ])
1201 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001202 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001203 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001204 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001205 boundary, _ = e.content.split(None, 1)
1206 self.assertEqual('--', boundary[:2])
1207 parts = e.content.split(boundary)
1208 self.assertEqual(4, len(parts))
1209 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001210 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001211 header = parts[1].splitlines()[1]
1212 self.assertEqual('Content-Type: application/http', header)
1213
Gabriel Garcia23174be2016-05-25 17:28:07 +02001214 def test_execute_initial_refresh_oauth2(self):
1215 batch = BatchHttpRequest()
1216 callbacks = Callbacks()
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -07001217 cred = MockCredentials('Foo', expired=True)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001218
1219 http = HttpMockSequence([
1220 ({'status': '200',
1221 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1222 BATCH_SINGLE_RESPONSE),
1223 ])
1224
1225 cred.authorize(http)
1226
1227 batch.add(self.request1, callback=callbacks.f)
1228 batch.execute(http=http)
1229
1230 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1231 self.assertIsNone(callbacks.exceptions['1'])
1232
1233 self.assertEqual(1, cred._refreshed)
1234
1235 self.assertEqual(1, cred._authorized)
1236
1237 self.assertEqual(1, cred._applied)
1238
Joe Gregorio654f4a22012-02-09 14:15:44 -05001239 def test_execute_refresh_and_retry_on_401(self):
1240 batch = BatchHttpRequest()
1241 callbacks = Callbacks()
1242 cred_1 = MockCredentials('Foo')
1243 cred_2 = MockCredentials('Bar')
1244
1245 http = HttpMockSequence([
1246 ({'status': '200',
1247 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1248 BATCH_RESPONSE_WITH_401),
1249 ({'status': '200',
1250 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1251 BATCH_SINGLE_RESPONSE),
1252 ])
1253
1254 creds_http_1 = HttpMockSequence([])
1255 cred_1.authorize(creds_http_1)
1256
1257 creds_http_2 = HttpMockSequence([])
1258 cred_2.authorize(creds_http_2)
1259
1260 self.request1.http = creds_http_1
1261 self.request2.http = creds_http_2
1262
1263 batch.add(self.request1, callback=callbacks.f)
1264 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001265 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001266
1267 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1268 self.assertEqual(None, callbacks.exceptions['1'])
1269 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1270 self.assertEqual(None, callbacks.exceptions['2'])
1271
1272 self.assertEqual(1, cred_1._refreshed)
1273 self.assertEqual(0, cred_2._refreshed)
1274
1275 self.assertEqual(1, cred_1._authorized)
1276 self.assertEqual(1, cred_2._authorized)
1277
1278 self.assertEqual(1, cred_2._applied)
1279 self.assertEqual(2, cred_1._applied)
1280
1281 def test_http_errors_passed_to_callback(self):
1282 batch = BatchHttpRequest()
1283 callbacks = Callbacks()
1284 cred_1 = MockCredentials('Foo')
1285 cred_2 = MockCredentials('Bar')
1286
1287 http = HttpMockSequence([
1288 ({'status': '200',
1289 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1290 BATCH_RESPONSE_WITH_401),
1291 ({'status': '200',
1292 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1293 BATCH_RESPONSE_WITH_401),
1294 ])
1295
1296 creds_http_1 = HttpMockSequence([])
1297 cred_1.authorize(creds_http_1)
1298
1299 creds_http_2 = HttpMockSequence([])
1300 cred_2.authorize(creds_http_2)
1301
1302 self.request1.http = creds_http_1
1303 self.request2.http = creds_http_2
1304
1305 batch.add(self.request1, callback=callbacks.f)
1306 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001307 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001308
1309 self.assertEqual(None, callbacks.responses['1'])
1310 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001311 self.assertEqual(
1312 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001313 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1314 self.assertEqual(None, callbacks.exceptions['2'])
1315
Joe Gregorio66f57522011-11-30 11:00:00 -05001316 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001317 callbacks = Callbacks()
1318 batch = BatchHttpRequest(callback=callbacks.f)
1319
1320 batch.add(self.request1)
1321 batch.add(self.request2)
1322 http = HttpMockSequence([
1323 ({'status': '200',
1324 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1325 BATCH_RESPONSE),
1326 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001327 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001328 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1329 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001330
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001331 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001332 callbacks = Callbacks()
1333 batch = BatchHttpRequest(callback=callbacks.f)
1334
1335 batch.add(self.request1)
1336 batch.add(self.request2)
1337 http = HttpMockSequence([
1338 ({'status': '200',
1339 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1340 BATCH_ERROR_RESPONSE),
1341 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001342 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001343 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1344 expected = ('<HttpError 403 when requesting '
1345 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1346 '"Access Not Configured">')
1347 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001348
Joe Gregorio5c120db2012-08-23 09:13:55 -04001349
Joe Gregorioba5c7902012-08-03 12:48:16 -04001350class TestRequestUriTooLong(unittest.TestCase):
1351
1352 def test_turn_get_into_post(self):
1353
1354 def _postproc(resp, content):
1355 return content
1356
1357 http = HttpMockSequence([
1358 ({'status': '200'},
1359 'echo_request_body'),
1360 ({'status': '200'},
1361 'echo_request_headers'),
1362 ])
1363
1364 # Send a long query parameter.
1365 query = {
1366 'q': 'a' * MAX_URI_LENGTH + '?&'
1367 }
1368 req = HttpRequest(
1369 http,
1370 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001371 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001372 method='GET',
1373 body=None,
1374 headers={},
1375 methodId='foo',
1376 resumable=None)
1377
1378 # Query parameters should be sent in the body.
1379 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001380 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001381
1382 # Extra headers should be set.
1383 response = req.execute()
1384 self.assertEqual('GET', response['x-http-method-override'])
1385 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1386 self.assertEqual(
1387 'application/x-www-form-urlencoded', response['content-type'])
1388
Joe Gregorio5c120db2012-08-23 09:13:55 -04001389
1390class TestStreamSlice(unittest.TestCase):
1391 """Test _StreamSlice."""
1392
1393 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001394 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001395
1396 def test_read(self):
1397 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001398 self.assertEqual(b'', s.read(0))
1399 self.assertEqual(b'0', s.read(1))
1400 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001401
1402 def test_read_too_much(self):
1403 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001404 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001405
1406 def test_read_all(self):
1407 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001408 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001409
Ali Afshar164f37e2013-01-07 14:05:45 -08001410
1411class TestResponseCallback(unittest.TestCase):
1412 """Test adding callbacks to responses."""
1413
1414 def test_ensure_response_callback(self):
1415 m = JsonModel()
1416 request = HttpRequest(
1417 None,
1418 m.response,
1419 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1420 method='POST',
1421 body='{}',
1422 headers={'content-type': 'application/json'})
1423 h = HttpMockSequence([ ({'status': 200}, '{}')])
1424 responses = []
1425 def _on_response(resp, responses=responses):
1426 responses.append(resp)
1427 request.add_response_callback(_on_response)
1428 request.execute(http=h)
1429 self.assertEqual(1, len(responses))
1430
1431
Craig Gurnik8e55b762015-01-20 15:00:10 -05001432class TestHttpMock(unittest.TestCase):
1433 def test_default_response_headers(self):
1434 http = HttpMock(datafile('zoo.json'))
1435 resp, content = http.request("http://example.com")
1436 self.assertEqual(resp.status, 200)
1437
Alan Briolat26b01002015-08-14 00:13:57 +01001438 def test_error_response(self):
1439 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1440 model = JsonModel()
1441 request = HttpRequest(
1442 http,
1443 model.response,
1444 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1445 method='GET',
1446 headers={})
1447 self.assertRaises(HttpError, request.execute)
1448
Craig Gurnik8e55b762015-01-20 15:00:10 -05001449
Igor Maravić22435292017-01-19 22:28:22 +01001450class TestHttpBuild(unittest.TestCase):
1451 original_socket_default_timeout = None
1452
1453 @classmethod
1454 def setUpClass(cls):
1455 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1456
1457 @classmethod
1458 def tearDownClass(cls):
1459 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1460
1461 def test_build_http_sets_default_timeout_if_none_specified(self):
1462 socket.setdefaulttimeout(None)
1463 http = build_http()
1464 self.assertIsInstance(http.timeout, int)
1465 self.assertGreater(http.timeout, 0)
1466
1467 def test_build_http_default_timeout_can_be_overridden(self):
1468 socket.setdefaulttimeout(1.5)
1469 http = build_http()
1470 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1471
1472 def test_build_http_default_timeout_can_be_set_to_zero(self):
1473 socket.setdefaulttimeout(0)
1474 http = build_http()
1475 self.assertEquals(http.timeout, 0)
1476
1477
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001478if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001479 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001480 unittest.main()