blob: f3374014416e801b9e9f907ce11a15c783b8519a [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
eesheeshc6425a02016-02-12 15:07:06 +0000128 if self.num_errors == 1:
129 raise ssl.SSLError()
130 else:
131 if PY3:
132 ex = TimeoutError()
133 else:
134 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200135 if self.num_errors == 2:
136 #first try a broken pipe error (#218)
137 ex.errno = socket.errno.EPIPE
138 else:
139 # Initialize the timeout error code to the platform's error code.
140 try:
141 # For Windows:
142 ex.errno = socket.errno.WSAETIMEDOUT
143 except AttributeError:
144 # For Linux/Mac:
145 ex.errno = socket.errno.ETIMEDOUT
146 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000147 raise ex
148
149
150class HttpMockWithNonRetriableErrors(object):
151 def __init__(self, num_errors, success_json, success_data):
152 self.num_errors = num_errors
153 self.success_json = success_json
154 self.success_data = success_data
155
156 def request(self, *args, **kwargs):
157 if not self.num_errors:
158 return httplib2.Response(self.success_json), self.success_data
159 else:
160 self.num_errors -= 1
161 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200162 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000163 try:
164 # For Windows:
165 ex.errno = socket.errno.WSAECONNREFUSED
166 except AttributeError:
167 # For Linux/Mac:
168 ex.errno = socket.errno.ECONNREFUSED
169 # Now raise the correct timeout error.
170 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100171
172
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500173DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
174
175
176def datafile(filename):
177 return os.path.join(DATA_DIR, filename)
178
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100179def _postproc_none(*kwargs):
180 pass
181
182
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500183class TestUserAgent(unittest.TestCase):
184
185 def test_set_user_agent(self):
186 http = HttpMockSequence([
187 ({'status': '200'}, 'echo_request_headers'),
188 ])
189
190 http = set_user_agent(http, "my_app/5.5")
191 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500192 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500193
194 def test_set_user_agent_nested(self):
195 http = HttpMockSequence([
196 ({'status': '200'}, 'echo_request_headers'),
197 ])
198
199 http = set_user_agent(http, "my_app/5.5")
200 http = set_user_agent(http, "my_library/0.1")
201 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500202 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500203
Joe Gregorio910b9b12012-06-12 09:36:30 -0400204
205class TestMediaUpload(unittest.TestCase):
206
Nam T. Nguyendc136312015-12-01 10:18:56 -0800207 def test_media_file_upload_mimetype_detection(self):
208 upload = MediaFileUpload(datafile('small.png'))
209 self.assertEqual('image/png', upload.mimetype())
210
211 upload = MediaFileUpload(datafile('empty'))
212 self.assertEqual('application/octet-stream', upload.mimetype())
213
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500214 def test_media_file_upload_to_from_json(self):
215 upload = MediaFileUpload(
216 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500217 self.assertEqual('image/png', upload.mimetype())
218 self.assertEqual(190, upload.size())
219 self.assertEqual(True, upload.resumable())
220 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800221 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500222
223 json = upload.to_json()
224 new_upload = MediaUpload.new_from_json(json)
225
Joe Gregorio654f4a22012-02-09 14:15:44 -0500226 self.assertEqual('image/png', new_upload.mimetype())
227 self.assertEqual(190, new_upload.size())
228 self.assertEqual(True, new_upload.resumable())
229 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800230 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500231
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400232 def test_media_file_upload_raises_on_invalid_chunksize(self):
233 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
234 datafile('small.png'), mimetype='image/png', chunksize=-2,
235 resumable=True)
236
Ali Afshar1cb6b672012-03-12 08:46:14 -0400237 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800238 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400239 resumable=True)
240 self.assertEqual('text/plain', media.mimetype())
241 self.assertEqual(10, media.chunksize())
242 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800243 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400244 self.assertEqual(6, media.size())
245
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500246 def test_http_request_to_from_json(self):
Igor Maravić22435292017-01-19 22:28:22 +0100247 http = build_http()
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500248 media_upload = MediaFileUpload(
249 datafile('small.png'), chunksize=500, resumable=True)
250 req = HttpRequest(
251 http,
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100252 _postproc_none,
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500253 'http://example.com',
254 method='POST',
255 body='{}',
256 headers={'content-type': 'multipart/related; boundary="---flubber"'},
257 methodId='foo',
258 resumable=media_upload)
259
260 json = req.to_json()
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100261 new_req = HttpRequest.from_json(json, http, _postproc_none)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500262
Joe Gregorio654f4a22012-02-09 14:15:44 -0500263 self.assertEqual({'content-type':
264 'multipart/related; boundary="---flubber"'},
265 new_req.headers)
266 self.assertEqual('http://example.com', new_req.uri)
267 self.assertEqual('{}', new_req.body)
268 self.assertEqual(http, new_req.http)
269 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500270
Joe Gregorio9086bd32013-06-14 16:32:05 -0400271 self.assertEqual(random.random, new_req._rand)
272 self.assertEqual(time.sleep, new_req._sleep)
273
Joe Gregorio910b9b12012-06-12 09:36:30 -0400274
275class TestMediaIoBaseUpload(unittest.TestCase):
276
277 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800278 fd = FileIO(datafile('small.png'), 'r')
279 upload = MediaIoBaseUpload(
280 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
281 self.assertEqual('image/png', upload.mimetype())
282 self.assertEqual(190, upload.size())
283 self.assertEqual(True, upload.resumable())
284 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800285 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400286
287 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800288 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400289 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400290 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400291 self.assertEqual('image/png', upload.mimetype())
292 self.assertEqual(190, upload.size())
293 self.assertEqual(True, upload.resumable())
294 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800295 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400296 f.close()
297
298 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800299 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400300 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400301
302 try:
303 json = upload.to_json()
304 self.fail('MediaIoBaseUpload should not be serializable.')
305 except NotImplementedError:
306 pass
307
Pat Feratec6050872015-03-03 18:24:59 -0800308 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400309 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800310 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800311 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400312 f.close()
313
314 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400315 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400316 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400317 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400318 self.assertEqual(True, upload.resumable())
319 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800320 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400321 f.close()
322
323 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800324 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800325 fd = BytesIO(f.read())
326 upload = MediaIoBaseUpload(
327 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
328 self.assertEqual('image/png', upload.mimetype())
329 self.assertEqual(190, upload.size())
330 self.assertEqual(True, upload.resumable())
331 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800332 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400333
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400334 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800335 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800336 fd = BytesIO(f.read())
337 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
338 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400339
340 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800341 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800342 upload = MediaIoBaseUpload(
343 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
344 self.assertEqual(True, upload.has_stream())
345 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400346
Joe Gregorio9086bd32013-06-14 16:32:05 -0400347 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800348 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800349 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400350 upload = MediaIoBaseUpload(
351 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
352
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500353 # Simulate errors for both the request that creates the resumable upload
354 # and the upload itself.
Joe Gregorio9086bd32013-06-14 16:32:05 -0400355 http = HttpMockSequence([
356 ({'status': '500'}, ''),
357 ({'status': '500'}, ''),
358 ({'status': '503'}, ''),
359 ({'status': '200', 'location': 'location'}, ''),
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500360 ({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
361 ({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE),
362 ({'status': '429'}, ''),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400363 ({'status': '200'}, '{}'),
364 ])
365
366 model = JsonModel()
367 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
368 method = u'POST'
369 request = HttpRequest(
370 http,
371 model.response,
372 uri,
373 method=method,
374 headers={},
375 resumable=upload)
376
377 sleeptimes = []
378 request._sleep = lambda x: sleeptimes.append(x)
379 request._rand = lambda: 10
380
381 request.execute(num_retries=3)
382 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
383
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500384 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
385 fd = BytesIO(b"i am png")
386 upload = MediaIoBaseUpload(
387 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
388
389 http = HttpMockSequence([
390 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
391 ({'status': '200'}, '{}')
392 ])
393
394 model = JsonModel()
395 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
396 method = u'POST'
397 request = HttpRequest(
398 http,
399 model.response,
400 uri,
401 method=method,
402 headers={},
403 resumable=upload)
404
405 request._rand = lambda: 1.0
406 request._sleep = mock.MagicMock()
407
408 with self.assertRaises(HttpError):
409 request.execute(num_retries=3)
410 request._sleep.assert_not_called()
411
Joe Gregorio910b9b12012-06-12 09:36:30 -0400412
Joe Gregorio708388c2012-06-15 13:43:04 -0400413class TestMediaIoBaseDownload(unittest.TestCase):
414
415 def setUp(self):
416 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400417 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400418 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800419 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400420
421 def test_media_io_base_download(self):
422 self.request.http = HttpMockSequence([
423 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800424 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400425 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800426 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400427 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400428 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400429
430 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400431 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400432
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400433 self.assertEqual(self.fd, download._fd)
434 self.assertEqual(3, download._chunksize)
435 self.assertEqual(0, download._progress)
436 self.assertEqual(None, download._total_size)
437 self.assertEqual(False, download._done)
438 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400439
440 status, done = download.next_chunk()
441
Pat Ferate2b140222015-03-03 18:05:11 -0800442 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400443 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400444 self.assertEqual(3, download._progress)
445 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400446 self.assertEqual(3, status.resumable_progress)
447
448 status, done = download.next_chunk()
449
Pat Ferate2b140222015-03-03 18:05:11 -0800450 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400451 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400452 self.assertEqual(5, download._progress)
453 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400454
455 def test_media_io_base_download_handle_redirects(self):
456 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400457 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800458 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400459 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800460 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400461 ])
462
463 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400464 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400465
466 status, done = download.next_chunk()
467
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400468 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400469
470 def test_media_io_base_download_handle_4xx(self):
471 self.request.http = HttpMockSequence([
472 ({'status': '400'}, ''),
473 ])
474
475 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400476 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400477
478 try:
479 status, done = download.next_chunk()
480 self.fail('Should raise an exception')
481 except HttpError:
482 pass
483
484 # Even after raising an exception we can pick up where we left off.
485 self.request.http = HttpMockSequence([
486 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800487 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400488 ])
489
490 status, done = download.next_chunk()
491
Pat Ferate2b140222015-03-03 18:05:11 -0800492 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400493
eesheeshc6425a02016-02-12 15:07:06 +0000494 def test_media_io_base_download_retries_connection_errors(self):
495 self.request.http = HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100496 3, {'status': '200', 'content-range': '0-2/3'}, b'123')
497
498 download = MediaIoBaseDownload(
499 fd=self.fd, request=self.request, chunksize=3)
500 download._sleep = lambda _x: 0 # do nothing
501 download._rand = lambda: 10
502
503 status, done = download.next_chunk(num_retries=3)
504
505 self.assertEqual(self.fd.getvalue(), b'123')
506 self.assertEqual(True, done)
507
Joe Gregorio9086bd32013-06-14 16:32:05 -0400508 def test_media_io_base_download_retries_5xx(self):
509 self.request.http = HttpMockSequence([
510 ({'status': '500'}, ''),
511 ({'status': '500'}, ''),
512 ({'status': '500'}, ''),
513 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800514 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400515 ({'status': '503'}, ''),
516 ({'status': '503'}, ''),
517 ({'status': '503'}, ''),
518 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800519 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400520 ])
521
522 download = MediaIoBaseDownload(
523 fd=self.fd, request=self.request, chunksize=3)
524
525 self.assertEqual(self.fd, download._fd)
526 self.assertEqual(3, download._chunksize)
527 self.assertEqual(0, download._progress)
528 self.assertEqual(None, download._total_size)
529 self.assertEqual(False, download._done)
530 self.assertEqual(self.request.uri, download._uri)
531
532 # Set time.sleep and random.random stubs.
533 sleeptimes = []
534 download._sleep = lambda x: sleeptimes.append(x)
535 download._rand = lambda: 10
536
537 status, done = download.next_chunk(num_retries=3)
538
539 # Check for exponential backoff using the rand function above.
540 self.assertEqual([20, 40, 80], sleeptimes)
541
Pat Ferate2b140222015-03-03 18:05:11 -0800542 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400543 self.assertEqual(False, done)
544 self.assertEqual(3, download._progress)
545 self.assertEqual(5, download._total_size)
546 self.assertEqual(3, status.resumable_progress)
547
548 # Reset time.sleep stub.
549 del sleeptimes[0:len(sleeptimes)]
550
551 status, done = download.next_chunk(num_retries=3)
552
553 # Check for exponential backoff using the rand function above.
554 self.assertEqual([20, 40, 80], sleeptimes)
555
Pat Ferate2b140222015-03-03 18:05:11 -0800556 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400557 self.assertEqual(True, done)
558 self.assertEqual(5, download._progress)
559 self.assertEqual(5, download._total_size)
560
andrewnestera4a44cf2017-03-31 16:09:31 +0300561 def test_media_io_base_download_empty_file(self):
562 self.request.http = HttpMockSequence([
563 ({'status': '200',
564 'content-range': '0-0/0'}, b''),
565 ])
566
567 download = MediaIoBaseDownload(
568 fd=self.fd, request=self.request, chunksize=3)
569
570 self.assertEqual(self.fd, download._fd)
571 self.assertEqual(0, download._progress)
572 self.assertEqual(None, download._total_size)
573 self.assertEqual(False, download._done)
574 self.assertEqual(self.request.uri, download._uri)
575
576 status, done = download.next_chunk()
577
578 self.assertEqual(True, done)
579 self.assertEqual(0, download._progress)
580 self.assertEqual(0, download._total_size)
581 self.assertEqual(0, status.progress())
582
Daniel44067782018-01-16 23:17:56 +0100583 def test_media_io_base_download_unknown_media_size(self):
584 self.request.http = HttpMockSequence([
585 ({'status': '200'}, b'123')
586 ])
587
588 download = MediaIoBaseDownload(
589 fd=self.fd, request=self.request, chunksize=3)
590
591 self.assertEqual(self.fd, download._fd)
592 self.assertEqual(0, download._progress)
593 self.assertEqual(None, download._total_size)
594 self.assertEqual(False, download._done)
595 self.assertEqual(self.request.uri, download._uri)
596
597 status, done = download.next_chunk()
598
599 self.assertEqual(self.fd.getvalue(), b'123')
600 self.assertEqual(True, done)
601 self.assertEqual(3, download._progress)
602 self.assertEqual(None, download._total_size)
603 self.assertEqual(0, status.progress())
604
605
Joe Gregorio66f57522011-11-30 11:00:00 -0500606EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
607Content-Type: application/json
608MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500609Host: www.googleapis.com
610content-length: 2\r\n\r\n{}"""
611
612
613NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
614Content-Type: application/json
615MIME-Version: 1.0
616Host: www.googleapis.com
617content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500618
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400619NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
620Content-Type: application/json
621MIME-Version: 1.0
622Host: www.googleapis.com\r\n\r\n"""
623
Joe Gregorio66f57522011-11-30 11:00:00 -0500624
625RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400626Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500627Content-Length: 14
628ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
629
630
INADA Naoki09157612015-03-25 01:51:03 +0900631BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500632Content-Type: application/http
633Content-Transfer-Encoding: binary
634Content-ID: <randomness+1>
635
636HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400637Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500638Content-Length: 14
639ETag: "etag/pony"\r\n\r\n{"foo": 42}
640
641--batch_foobarbaz
642Content-Type: application/http
643Content-Transfer-Encoding: binary
644Content-ID: <randomness+2>
645
646HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400647Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500648Content-Length: 14
649ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
650--batch_foobarbaz--"""
651
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500652
INADA Naoki09157612015-03-25 01:51:03 +0900653BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400654Content-Type: application/http
655Content-Transfer-Encoding: binary
656Content-ID: <randomness+1>
657
658HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400659Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400660Content-Length: 14
661ETag: "etag/pony"\r\n\r\n{"foo": 42}
662
663--batch_foobarbaz
664Content-Type: application/http
665Content-Transfer-Encoding: binary
666Content-ID: <randomness+2>
667
668HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400669Content-Type: application/json
670Content-Length: 245
671ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400672 "error": {
673 "errors": [
674 {
675 "domain": "usageLimits",
676 "reason": "accessNotConfigured",
677 "message": "Access Not Configured",
678 "debugInfo": "QuotaState: BLOCKED"
679 }
680 ],
681 "code": 403,
682 "message": "Access Not Configured"
683 }
684}
685
686--batch_foobarbaz--"""
687
688
INADA Naoki09157612015-03-25 01:51:03 +0900689BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500690Content-Type: application/http
691Content-Transfer-Encoding: binary
692Content-ID: <randomness+1>
693
Joe Gregorioc752e332012-07-11 14:43:52 -0400694HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400695Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500696Content-Length: 14
697ETag: "etag/pony"\r\n\r\n{"error": {"message":
698 "Authorizaton failed."}}
699
700--batch_foobarbaz
701Content-Type: application/http
702Content-Transfer-Encoding: binary
703Content-ID: <randomness+2>
704
705HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400706Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500707Content-Length: 14
708ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
709--batch_foobarbaz--"""
710
711
INADA Naoki09157612015-03-25 01:51:03 +0900712BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500713Content-Type: application/http
714Content-Transfer-Encoding: binary
715Content-ID: <randomness+1>
716
717HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400718Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500719Content-Length: 14
720ETag: "etag/pony"\r\n\r\n{"foo": 42}
721--batch_foobarbaz--"""
722
eesheeshc6425a02016-02-12 15:07:06 +0000723
724USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
725 "error": {
726 "errors": [
727 {
728 "domain": "usageLimits",
729 "reason": "userRateLimitExceeded",
730 "message": "User Rate Limit Exceeded"
731 }
732 ],
733 "code": 403,
734 "message": "User Rate Limit Exceeded"
735 }
736}"""
737
738
739RATE_LIMIT_EXCEEDED_RESPONSE = """{
740 "error": {
741 "errors": [
742 {
743 "domain": "usageLimits",
744 "reason": "rateLimitExceeded",
745 "message": "Rate Limit Exceeded"
746 }
747 ],
748 "code": 403,
749 "message": "Rate Limit Exceeded"
750 }
751}"""
752
753
754NOT_CONFIGURED_RESPONSE = """{
755 "error": {
756 "errors": [
757 {
758 "domain": "usageLimits",
759 "reason": "accessNotConfigured",
760 "message": "Access Not Configured"
761 }
762 ],
763 "code": 403,
764 "message": "Access Not Configured"
765 }
766}"""
767
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800768LIST_NOT_CONFIGURED_RESPONSE = """[
769 "error": {
770 "errors": [
771 {
772 "domain": "usageLimits",
773 "reason": "accessNotConfigured",
774 "message": "Access Not Configured"
775 }
776 ],
777 "code": 403,
778 "message": "Access Not Configured"
779 }
780]"""
781
Joe Gregorio654f4a22012-02-09 14:15:44 -0500782class Callbacks(object):
783 def __init__(self):
784 self.responses = {}
785 self.exceptions = {}
786
787 def f(self, request_id, response, exception):
788 self.responses[request_id] = response
789 self.exceptions[request_id] = exception
790
791
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500792class TestHttpRequest(unittest.TestCase):
793 def test_unicode(self):
794 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
795 model = JsonModel()
796 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
797 method = u'POST'
798 request = HttpRequest(
799 http,
800 model.response,
801 uri,
802 method=method,
803 body=u'{}',
804 headers={'content-type': 'application/json'})
805 request.execute()
806 self.assertEqual(uri, http.uri)
807 self.assertEqual(str, type(http.uri))
808 self.assertEqual(method, http.method)
809 self.assertEqual(str, type(http.method))
810
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100811 def test_empty_content_type(self):
812 """Test for #284"""
813 http = HttpMock(None, headers={'status': 200})
814 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
815 method = u'POST'
816 request = HttpRequest(
817 http,
818 _postproc_none,
819 uri,
820 method=method,
821 headers={'content-type': ''})
822 request.execute()
823 self.assertEqual('', http.headers.get('content-type'))
824
eesheeshc6425a02016-02-12 15:07:06 +0000825 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100826 model = JsonModel()
827 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000828 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
829 model.response,
830 u'https://www.example.com/json_api_endpoint')
831 request._sleep = lambda _x: 0 # do nothing
832 request._rand = lambda: 10
833 with self.assertRaises(socket.error):
834 response = request.execute(num_retries=3)
835
836
837 def test_retry_connection_errors_non_resumable(self):
838 model = JsonModel()
839 request = HttpRequest(
840 HttpMockWithErrors(3, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100841 model.response,
842 u'https://www.example.com/json_api_endpoint')
843 request._sleep = lambda _x: 0 # do nothing
844 request._rand = lambda: 10
845 response = request.execute(num_retries=3)
846 self.assertEqual({u'foo': u'bar'}, response)
847
eesheeshc6425a02016-02-12 15:07:06 +0000848 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100849 with open(datafile('small.png'), 'rb') as small_png_file:
850 small_png_fd = BytesIO(small_png_file.read())
851 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
852 chunksize=500, resumable=True)
853 model = JsonModel()
854
855 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000856 HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100857 3, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
858 model.response,
859 u'https://www.example.com/file_upload',
860 method='POST',
861 resumable=upload)
862 request._sleep = lambda _x: 0 # do nothing
863 request._rand = lambda: 10
864 response = request.execute(num_retries=3)
865 self.assertEqual({u'foo': u'bar'}, response)
866
Joe Gregorio9086bd32013-06-14 16:32:05 -0400867 def test_retry(self):
868 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000869 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
870 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
871 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
872 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400873 resp_seq.append(({'status': '200'}, '{}'))
874
875 http = HttpMockSequence(resp_seq)
876 model = JsonModel()
877 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
878 method = u'POST'
879 request = HttpRequest(
880 http,
881 model.response,
882 uri,
883 method=method,
884 body=u'{}',
885 headers={'content-type': 'application/json'})
886
887 sleeptimes = []
888 request._sleep = lambda x: sleeptimes.append(x)
889 request._rand = lambda: 10
890
891 request.execute(num_retries=num_retries)
892
893 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900894 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400895 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
896
eesheeshc6425a02016-02-12 15:07:06 +0000897 def test_no_retry_succeeds(self):
898 num_retries = 5
899 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
900
901 http = HttpMockSequence(resp_seq)
902 model = JsonModel()
903 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
904 method = u'POST'
905 request = HttpRequest(
906 http,
907 model.response,
908 uri,
909 method=method,
910 body=u'{}',
911 headers={'content-type': 'application/json'})
912
913 sleeptimes = []
914 request._sleep = lambda x: sleeptimes.append(x)
915 request._rand = lambda: 10
916
917 request.execute(num_retries=num_retries)
918
919 self.assertEqual(0, len(sleeptimes))
920
Joe Gregorio9086bd32013-06-14 16:32:05 -0400921 def test_no_retry_fails_fast(self):
922 http = HttpMockSequence([
923 ({'status': '500'}, ''),
924 ({'status': '200'}, '{}')
925 ])
926 model = JsonModel()
927 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
928 method = u'POST'
929 request = HttpRequest(
930 http,
931 model.response,
932 uri,
933 method=method,
934 body=u'{}',
935 headers={'content-type': 'application/json'})
936
937 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000938 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400939
eesheeshc6425a02016-02-12 15:07:06 +0000940 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400941 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000942 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400943
eesheeshc6425a02016-02-12 15:07:06 +0000944 def test_no_retry_403_not_configured_fails_fast(self):
945 http = HttpMockSequence([
946 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
947 ({'status': '200'}, '{}')
948 ])
949 model = JsonModel()
950 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
951 method = u'POST'
952 request = HttpRequest(
953 http,
954 model.response,
955 uri,
956 method=method,
957 body=u'{}',
958 headers={'content-type': 'application/json'})
959
960 request._rand = lambda: 1.0
961 request._sleep = mock.MagicMock()
962
963 with self.assertRaises(HttpError):
964 request.execute()
965 request._sleep.assert_not_called()
966
967 def test_no_retry_403_fails_fast(self):
968 http = HttpMockSequence([
969 ({'status': '403'}, ''),
970 ({'status': '200'}, '{}')
971 ])
972 model = JsonModel()
973 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
974 method = u'POST'
975 request = HttpRequest(
976 http,
977 model.response,
978 uri,
979 method=method,
980 body=u'{}',
981 headers={'content-type': 'application/json'})
982
983 request._rand = lambda: 1.0
984 request._sleep = mock.MagicMock()
985
986 with self.assertRaises(HttpError):
987 request.execute()
988 request._sleep.assert_not_called()
989
990 def test_no_retry_401_fails_fast(self):
991 http = HttpMockSequence([
992 ({'status': '401'}, ''),
993 ({'status': '200'}, '{}')
994 ])
995 model = JsonModel()
996 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
997 method = u'POST'
998 request = HttpRequest(
999 http,
1000 model.response,
1001 uri,
1002 method=method,
1003 body=u'{}',
1004 headers={'content-type': 'application/json'})
1005
1006 request._rand = lambda: 1.0
1007 request._sleep = mock.MagicMock()
1008
1009 with self.assertRaises(HttpError):
1010 request.execute()
1011 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001012
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001013 def test_no_retry_403_list_fails(self):
1014 http = HttpMockSequence([
1015 ({'status': '403'}, LIST_NOT_CONFIGURED_RESPONSE),
1016 ({'status': '200'}, '{}')
1017 ])
1018 model = JsonModel()
1019 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1020 method = u'POST'
1021 request = HttpRequest(
1022 http,
1023 model.response,
1024 uri,
1025 method=method,
1026 body=u'{}',
1027 headers={'content-type': 'application/json'})
1028
1029 request._rand = lambda: 1.0
1030 request._sleep = mock.MagicMock()
1031
1032 with self.assertRaises(HttpError):
1033 request.execute()
1034 request._sleep.assert_not_called()
1035
Joe Gregorio66f57522011-11-30 11:00:00 -05001036class TestBatch(unittest.TestCase):
1037
1038 def setUp(self):
1039 model = JsonModel()
1040 self.request1 = HttpRequest(
1041 None,
1042 model.response,
1043 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1044 method='POST',
1045 body='{}',
1046 headers={'content-type': 'application/json'})
1047
1048 self.request2 = HttpRequest(
1049 None,
1050 model.response,
1051 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001052 method='GET',
1053 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -05001054 headers={'content-type': 'application/json'})
1055
1056
1057 def test_id_to_from_content_id_header(self):
1058 batch = BatchHttpRequest()
1059 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
1060
1061 def test_invalid_content_id_header(self):
1062 batch = BatchHttpRequest()
1063 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
1064 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
1065 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
1066
1067 def test_serialize_request(self):
1068 batch = BatchHttpRequest()
1069 request = HttpRequest(
1070 None,
1071 None,
1072 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1073 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001074 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -05001075 headers={'content-type': 'application/json'},
1076 methodId=None,
1077 resumable=None)
1078 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001079 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001080
Joe Gregoriodd813822012-01-25 10:32:47 -05001081 def test_serialize_request_media_body(self):
1082 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001083 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001084 body = f.read()
1085 f.close()
1086
1087 request = HttpRequest(
1088 None,
1089 None,
1090 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1091 method='POST',
1092 body=body,
1093 headers={'content-type': 'application/json'},
1094 methodId=None,
1095 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001096 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001097 s = batch._serialize_request(request).splitlines()
1098
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001099 def test_serialize_request_no_body(self):
1100 batch = BatchHttpRequest()
1101 request = HttpRequest(
1102 None,
1103 None,
1104 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1105 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001106 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001107 headers={'content-type': 'application/json'},
1108 methodId=None,
1109 resumable=None)
1110 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001111 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001112
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001113 def test_serialize_get_request_no_body(self):
1114 batch = BatchHttpRequest()
1115 request = HttpRequest(
1116 None,
1117 None,
1118 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1119 method='GET',
1120 body=None,
1121 headers={'content-type': 'application/json'},
1122 methodId=None,
1123 resumable=None)
1124 s = batch._serialize_request(request).splitlines()
1125 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1126
Joe Gregorio66f57522011-11-30 11:00:00 -05001127 def test_deserialize_response(self):
1128 batch = BatchHttpRequest()
1129 resp, content = batch._deserialize_response(RESPONSE)
1130
Joe Gregorio654f4a22012-02-09 14:15:44 -05001131 self.assertEqual(200, resp.status)
1132 self.assertEqual('OK', resp.reason)
1133 self.assertEqual(11, resp.version)
1134 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001135
1136 def test_new_id(self):
1137 batch = BatchHttpRequest()
1138
1139 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001140 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001141
1142 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001143 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001144
1145 batch.add(self.request1, request_id='3')
1146
1147 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001148 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001149
1150 def test_add(self):
1151 batch = BatchHttpRequest()
1152 batch.add(self.request1, request_id='1')
1153 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1154
1155 def test_add_fail_for_resumable(self):
1156 batch = BatchHttpRequest()
1157
1158 upload = MediaFileUpload(
1159 datafile('small.png'), chunksize=500, resumable=True)
1160 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001161 with self.assertRaises(BatchError) as batch_error:
1162 batch.add(self.request1, request_id='1')
1163 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001164
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001165 def test_execute_empty_batch_no_http(self):
1166 batch = BatchHttpRequest()
1167 ret = batch.execute()
1168 self.assertEqual(None, ret)
1169
Joe Gregorio66f57522011-11-30 11:00:00 -05001170 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001171 batch = BatchHttpRequest()
1172 callbacks = Callbacks()
1173
1174 batch.add(self.request1, callback=callbacks.f)
1175 batch.add(self.request2, callback=callbacks.f)
1176 http = HttpMockSequence([
1177 ({'status': '200',
1178 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1179 BATCH_RESPONSE),
1180 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001181 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001182 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1183 self.assertEqual(None, callbacks.exceptions['1'])
1184 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1185 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001186
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001187 def test_execute_request_body(self):
1188 batch = BatchHttpRequest()
1189
1190 batch.add(self.request1)
1191 batch.add(self.request2)
1192 http = HttpMockSequence([
1193 ({'status': '200',
1194 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1195 'echo_request_body'),
1196 ])
1197 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001198 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001199 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001200 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001201 boundary, _ = e.content.split(None, 1)
1202 self.assertEqual('--', boundary[:2])
1203 parts = e.content.split(boundary)
1204 self.assertEqual(4, len(parts))
1205 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001206 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001207 header = parts[1].splitlines()[1]
1208 self.assertEqual('Content-Type: application/http', header)
1209
Gabriel Garcia23174be2016-05-25 17:28:07 +02001210 def test_execute_initial_refresh_oauth2(self):
1211 batch = BatchHttpRequest()
1212 callbacks = Callbacks()
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -07001213 cred = MockCredentials('Foo', expired=True)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001214
1215 http = HttpMockSequence([
1216 ({'status': '200',
1217 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1218 BATCH_SINGLE_RESPONSE),
1219 ])
1220
1221 cred.authorize(http)
1222
1223 batch.add(self.request1, callback=callbacks.f)
1224 batch.execute(http=http)
1225
1226 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1227 self.assertIsNone(callbacks.exceptions['1'])
1228
1229 self.assertEqual(1, cred._refreshed)
1230
1231 self.assertEqual(1, cred._authorized)
1232
1233 self.assertEqual(1, cred._applied)
1234
Joe Gregorio654f4a22012-02-09 14:15:44 -05001235 def test_execute_refresh_and_retry_on_401(self):
1236 batch = BatchHttpRequest()
1237 callbacks = Callbacks()
1238 cred_1 = MockCredentials('Foo')
1239 cred_2 = MockCredentials('Bar')
1240
1241 http = HttpMockSequence([
1242 ({'status': '200',
1243 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1244 BATCH_RESPONSE_WITH_401),
1245 ({'status': '200',
1246 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1247 BATCH_SINGLE_RESPONSE),
1248 ])
1249
1250 creds_http_1 = HttpMockSequence([])
1251 cred_1.authorize(creds_http_1)
1252
1253 creds_http_2 = HttpMockSequence([])
1254 cred_2.authorize(creds_http_2)
1255
1256 self.request1.http = creds_http_1
1257 self.request2.http = creds_http_2
1258
1259 batch.add(self.request1, callback=callbacks.f)
1260 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001261 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001262
1263 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1264 self.assertEqual(None, callbacks.exceptions['1'])
1265 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1266 self.assertEqual(None, callbacks.exceptions['2'])
1267
1268 self.assertEqual(1, cred_1._refreshed)
1269 self.assertEqual(0, cred_2._refreshed)
1270
1271 self.assertEqual(1, cred_1._authorized)
1272 self.assertEqual(1, cred_2._authorized)
1273
1274 self.assertEqual(1, cred_2._applied)
1275 self.assertEqual(2, cred_1._applied)
1276
1277 def test_http_errors_passed_to_callback(self):
1278 batch = BatchHttpRequest()
1279 callbacks = Callbacks()
1280 cred_1 = MockCredentials('Foo')
1281 cred_2 = MockCredentials('Bar')
1282
1283 http = HttpMockSequence([
1284 ({'status': '200',
1285 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1286 BATCH_RESPONSE_WITH_401),
1287 ({'status': '200',
1288 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1289 BATCH_RESPONSE_WITH_401),
1290 ])
1291
1292 creds_http_1 = HttpMockSequence([])
1293 cred_1.authorize(creds_http_1)
1294
1295 creds_http_2 = HttpMockSequence([])
1296 cred_2.authorize(creds_http_2)
1297
1298 self.request1.http = creds_http_1
1299 self.request2.http = creds_http_2
1300
1301 batch.add(self.request1, callback=callbacks.f)
1302 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001303 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001304
1305 self.assertEqual(None, callbacks.responses['1'])
1306 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001307 self.assertEqual(
1308 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001309 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1310 self.assertEqual(None, callbacks.exceptions['2'])
1311
Joe Gregorio66f57522011-11-30 11:00:00 -05001312 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001313 callbacks = Callbacks()
1314 batch = BatchHttpRequest(callback=callbacks.f)
1315
1316 batch.add(self.request1)
1317 batch.add(self.request2)
1318 http = HttpMockSequence([
1319 ({'status': '200',
1320 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1321 BATCH_RESPONSE),
1322 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001323 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001324 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1325 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001326
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001327 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001328 callbacks = Callbacks()
1329 batch = BatchHttpRequest(callback=callbacks.f)
1330
1331 batch.add(self.request1)
1332 batch.add(self.request2)
1333 http = HttpMockSequence([
1334 ({'status': '200',
1335 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1336 BATCH_ERROR_RESPONSE),
1337 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001338 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001339 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1340 expected = ('<HttpError 403 when requesting '
1341 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1342 '"Access Not Configured">')
1343 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001344
Joe Gregorio5c120db2012-08-23 09:13:55 -04001345
Joe Gregorioba5c7902012-08-03 12:48:16 -04001346class TestRequestUriTooLong(unittest.TestCase):
1347
1348 def test_turn_get_into_post(self):
1349
1350 def _postproc(resp, content):
1351 return content
1352
1353 http = HttpMockSequence([
1354 ({'status': '200'},
1355 'echo_request_body'),
1356 ({'status': '200'},
1357 'echo_request_headers'),
1358 ])
1359
1360 # Send a long query parameter.
1361 query = {
1362 'q': 'a' * MAX_URI_LENGTH + '?&'
1363 }
1364 req = HttpRequest(
1365 http,
1366 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001367 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001368 method='GET',
1369 body=None,
1370 headers={},
1371 methodId='foo',
1372 resumable=None)
1373
1374 # Query parameters should be sent in the body.
1375 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001376 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001377
1378 # Extra headers should be set.
1379 response = req.execute()
1380 self.assertEqual('GET', response['x-http-method-override'])
1381 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1382 self.assertEqual(
1383 'application/x-www-form-urlencoded', response['content-type'])
1384
Joe Gregorio5c120db2012-08-23 09:13:55 -04001385
1386class TestStreamSlice(unittest.TestCase):
1387 """Test _StreamSlice."""
1388
1389 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001390 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001391
1392 def test_read(self):
1393 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001394 self.assertEqual(b'', s.read(0))
1395 self.assertEqual(b'0', s.read(1))
1396 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001397
1398 def test_read_too_much(self):
1399 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001400 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001401
1402 def test_read_all(self):
1403 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001404 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001405
Ali Afshar164f37e2013-01-07 14:05:45 -08001406
1407class TestResponseCallback(unittest.TestCase):
1408 """Test adding callbacks to responses."""
1409
1410 def test_ensure_response_callback(self):
1411 m = JsonModel()
1412 request = HttpRequest(
1413 None,
1414 m.response,
1415 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1416 method='POST',
1417 body='{}',
1418 headers={'content-type': 'application/json'})
1419 h = HttpMockSequence([ ({'status': 200}, '{}')])
1420 responses = []
1421 def _on_response(resp, responses=responses):
1422 responses.append(resp)
1423 request.add_response_callback(_on_response)
1424 request.execute(http=h)
1425 self.assertEqual(1, len(responses))
1426
1427
Craig Gurnik8e55b762015-01-20 15:00:10 -05001428class TestHttpMock(unittest.TestCase):
1429 def test_default_response_headers(self):
1430 http = HttpMock(datafile('zoo.json'))
1431 resp, content = http.request("http://example.com")
1432 self.assertEqual(resp.status, 200)
1433
Alan Briolat26b01002015-08-14 00:13:57 +01001434 def test_error_response(self):
1435 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1436 model = JsonModel()
1437 request = HttpRequest(
1438 http,
1439 model.response,
1440 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1441 method='GET',
1442 headers={})
1443 self.assertRaises(HttpError, request.execute)
1444
Craig Gurnik8e55b762015-01-20 15:00:10 -05001445
Igor Maravić22435292017-01-19 22:28:22 +01001446class TestHttpBuild(unittest.TestCase):
1447 original_socket_default_timeout = None
1448
1449 @classmethod
1450 def setUpClass(cls):
1451 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1452
1453 @classmethod
1454 def tearDownClass(cls):
1455 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1456
1457 def test_build_http_sets_default_timeout_if_none_specified(self):
1458 socket.setdefaulttimeout(None)
1459 http = build_http()
1460 self.assertIsInstance(http.timeout, int)
1461 self.assertGreater(http.timeout, 0)
1462
1463 def test_build_http_default_timeout_can_be_overridden(self):
1464 socket.setdefaulttimeout(1.5)
1465 http = build_http()
1466 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1467
1468 def test_build_http_default_timeout_can_be_set_to_zero(self):
1469 socket.setdefaulttimeout(0)
1470 http = build_http()
1471 self.assertEquals(http.timeout, 0)
1472
1473
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001474if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001475 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001476 unittest.main()