blob: f6beff7121a9dc04621bada08fd12e95b952d317 [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()
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700130 else: # initial != 2
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200131 if self.num_errors == 2:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700132 # first try a broken pipe error (#218)
133 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200134 ex.errno = socket.errno.EPIPE
135 else:
136 # Initialize the timeout error code to the platform's error code.
137 try:
138 # For Windows:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700139 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200140 ex.errno = socket.errno.WSAETIMEDOUT
141 except AttributeError:
142 # For Linux/Mac:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700143 if PY3:
144 ex = socket.timeout()
145 else:
146 ex = socket.error()
147 ex.errno = socket.errno.ETIMEDOUT
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200148 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000149 raise ex
150
151
152class HttpMockWithNonRetriableErrors(object):
153 def __init__(self, num_errors, success_json, success_data):
154 self.num_errors = num_errors
155 self.success_json = success_json
156 self.success_data = success_data
157
158 def request(self, *args, **kwargs):
159 if not self.num_errors:
160 return httplib2.Response(self.success_json), self.success_data
161 else:
162 self.num_errors -= 1
163 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200164 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000165 try:
166 # For Windows:
167 ex.errno = socket.errno.WSAECONNREFUSED
168 except AttributeError:
169 # For Linux/Mac:
170 ex.errno = socket.errno.ECONNREFUSED
171 # Now raise the correct timeout error.
172 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100173
174
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500175DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
176
177
178def datafile(filename):
179 return os.path.join(DATA_DIR, filename)
180
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100181def _postproc_none(*kwargs):
182 pass
183
184
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500185class TestUserAgent(unittest.TestCase):
186
187 def test_set_user_agent(self):
188 http = HttpMockSequence([
189 ({'status': '200'}, 'echo_request_headers'),
190 ])
191
192 http = set_user_agent(http, "my_app/5.5")
193 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500194 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500195
196 def test_set_user_agent_nested(self):
197 http = HttpMockSequence([
198 ({'status': '200'}, 'echo_request_headers'),
199 ])
200
201 http = set_user_agent(http, "my_app/5.5")
202 http = set_user_agent(http, "my_library/0.1")
203 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500204 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500205
Joe Gregorio910b9b12012-06-12 09:36:30 -0400206
207class TestMediaUpload(unittest.TestCase):
208
Nam T. Nguyendc136312015-12-01 10:18:56 -0800209 def test_media_file_upload_mimetype_detection(self):
210 upload = MediaFileUpload(datafile('small.png'))
211 self.assertEqual('image/png', upload.mimetype())
212
213 upload = MediaFileUpload(datafile('empty'))
214 self.assertEqual('application/octet-stream', upload.mimetype())
215
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500216 def test_media_file_upload_to_from_json(self):
217 upload = MediaFileUpload(
218 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500219 self.assertEqual('image/png', upload.mimetype())
220 self.assertEqual(190, upload.size())
221 self.assertEqual(True, upload.resumable())
222 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800223 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500224
225 json = upload.to_json()
226 new_upload = MediaUpload.new_from_json(json)
227
Joe Gregorio654f4a22012-02-09 14:15:44 -0500228 self.assertEqual('image/png', new_upload.mimetype())
229 self.assertEqual(190, new_upload.size())
230 self.assertEqual(True, new_upload.resumable())
231 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800232 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500233
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400234 def test_media_file_upload_raises_on_invalid_chunksize(self):
235 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
236 datafile('small.png'), mimetype='image/png', chunksize=-2,
237 resumable=True)
238
Ali Afshar1cb6b672012-03-12 08:46:14 -0400239 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800240 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400241 resumable=True)
242 self.assertEqual('text/plain', media.mimetype())
243 self.assertEqual(10, media.chunksize())
244 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800245 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400246 self.assertEqual(6, media.size())
247
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500248 def test_http_request_to_from_json(self):
Igor Maravić22435292017-01-19 22:28:22 +0100249 http = build_http()
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500250 media_upload = MediaFileUpload(
251 datafile('small.png'), chunksize=500, resumable=True)
252 req = HttpRequest(
253 http,
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100254 _postproc_none,
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500255 'http://example.com',
256 method='POST',
257 body='{}',
258 headers={'content-type': 'multipart/related; boundary="---flubber"'},
259 methodId='foo',
260 resumable=media_upload)
261
262 json = req.to_json()
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100263 new_req = HttpRequest.from_json(json, http, _postproc_none)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500264
Joe Gregorio654f4a22012-02-09 14:15:44 -0500265 self.assertEqual({'content-type':
266 'multipart/related; boundary="---flubber"'},
267 new_req.headers)
268 self.assertEqual('http://example.com', new_req.uri)
269 self.assertEqual('{}', new_req.body)
270 self.assertEqual(http, new_req.http)
271 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500272
Joe Gregorio9086bd32013-06-14 16:32:05 -0400273 self.assertEqual(random.random, new_req._rand)
274 self.assertEqual(time.sleep, new_req._sleep)
275
Joe Gregorio910b9b12012-06-12 09:36:30 -0400276
277class TestMediaIoBaseUpload(unittest.TestCase):
278
279 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800280 fd = FileIO(datafile('small.png'), 'r')
281 upload = MediaIoBaseUpload(
282 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
283 self.assertEqual('image/png', upload.mimetype())
284 self.assertEqual(190, upload.size())
285 self.assertEqual(True, upload.resumable())
286 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800287 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400288
289 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800290 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400291 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400292 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400293 self.assertEqual('image/png', upload.mimetype())
294 self.assertEqual(190, upload.size())
295 self.assertEqual(True, upload.resumable())
296 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800297 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400298 f.close()
299
300 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800301 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400302 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400303
304 try:
305 json = upload.to_json()
306 self.fail('MediaIoBaseUpload should not be serializable.')
307 except NotImplementedError:
308 pass
309
Pat Feratec6050872015-03-03 18:24:59 -0800310 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400311 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800312 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800313 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400314 f.close()
315
316 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400317 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400318 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400319 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400320 self.assertEqual(True, upload.resumable())
321 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800322 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400323 f.close()
324
325 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800326 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800327 fd = BytesIO(f.read())
328 upload = MediaIoBaseUpload(
329 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
330 self.assertEqual('image/png', upload.mimetype())
331 self.assertEqual(190, upload.size())
332 self.assertEqual(True, upload.resumable())
333 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800334 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400335
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400336 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800337 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800338 fd = BytesIO(f.read())
339 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
340 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400341
342 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800343 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800344 upload = MediaIoBaseUpload(
345 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
346 self.assertEqual(True, upload.has_stream())
347 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400348
Joe Gregorio9086bd32013-06-14 16:32:05 -0400349 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800350 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800351 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400352 upload = MediaIoBaseUpload(
353 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
354
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500355 # Simulate errors for both the request that creates the resumable upload
356 # and the upload itself.
Joe Gregorio9086bd32013-06-14 16:32:05 -0400357 http = HttpMockSequence([
358 ({'status': '500'}, ''),
359 ({'status': '500'}, ''),
360 ({'status': '503'}, ''),
361 ({'status': '200', 'location': 'location'}, ''),
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500362 ({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
363 ({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE),
364 ({'status': '429'}, ''),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400365 ({'status': '200'}, '{}'),
366 ])
367
368 model = JsonModel()
369 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
370 method = u'POST'
371 request = HttpRequest(
372 http,
373 model.response,
374 uri,
375 method=method,
376 headers={},
377 resumable=upload)
378
379 sleeptimes = []
380 request._sleep = lambda x: sleeptimes.append(x)
381 request._rand = lambda: 10
382
383 request.execute(num_retries=3)
384 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
385
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500386 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
387 fd = BytesIO(b"i am png")
388 upload = MediaIoBaseUpload(
389 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
390
391 http = HttpMockSequence([
392 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
393 ({'status': '200'}, '{}')
394 ])
395
396 model = JsonModel()
397 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
398 method = u'POST'
399 request = HttpRequest(
400 http,
401 model.response,
402 uri,
403 method=method,
404 headers={},
405 resumable=upload)
406
407 request._rand = lambda: 1.0
408 request._sleep = mock.MagicMock()
409
410 with self.assertRaises(HttpError):
411 request.execute(num_retries=3)
412 request._sleep.assert_not_called()
413
Joe Gregorio910b9b12012-06-12 09:36:30 -0400414
Joe Gregorio708388c2012-06-15 13:43:04 -0400415class TestMediaIoBaseDownload(unittest.TestCase):
416
417 def setUp(self):
418 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400419 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400420 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800421 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400422
423 def test_media_io_base_download(self):
424 self.request.http = HttpMockSequence([
425 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800426 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400427 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800428 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400429 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400430 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400431
432 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400433 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400434
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400435 self.assertEqual(self.fd, download._fd)
436 self.assertEqual(3, download._chunksize)
437 self.assertEqual(0, download._progress)
438 self.assertEqual(None, download._total_size)
439 self.assertEqual(False, download._done)
440 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400441
442 status, done = download.next_chunk()
443
Pat Ferate2b140222015-03-03 18:05:11 -0800444 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400445 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400446 self.assertEqual(3, download._progress)
447 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400448 self.assertEqual(3, status.resumable_progress)
449
450 status, done = download.next_chunk()
451
Pat Ferate2b140222015-03-03 18:05:11 -0800452 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400453 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400454 self.assertEqual(5, download._progress)
455 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400456
457 def test_media_io_base_download_handle_redirects(self):
458 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400459 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800460 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400461 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800462 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400463 ])
464
465 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400466 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400467
468 status, done = download.next_chunk()
469
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400470 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400471
472 def test_media_io_base_download_handle_4xx(self):
473 self.request.http = HttpMockSequence([
474 ({'status': '400'}, ''),
475 ])
476
477 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400478 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400479
480 try:
481 status, done = download.next_chunk()
482 self.fail('Should raise an exception')
483 except HttpError:
484 pass
485
486 # Even after raising an exception we can pick up where we left off.
487 self.request.http = HttpMockSequence([
488 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800489 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400490 ])
491
492 status, done = download.next_chunk()
493
Pat Ferate2b140222015-03-03 18:05:11 -0800494 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400495
eesheeshc6425a02016-02-12 15:07:06 +0000496 def test_media_io_base_download_retries_connection_errors(self):
497 self.request.http = HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100498 3, {'status': '200', 'content-range': '0-2/3'}, b'123')
499
500 download = MediaIoBaseDownload(
501 fd=self.fd, request=self.request, chunksize=3)
502 download._sleep = lambda _x: 0 # do nothing
503 download._rand = lambda: 10
504
505 status, done = download.next_chunk(num_retries=3)
506
507 self.assertEqual(self.fd.getvalue(), b'123')
508 self.assertEqual(True, done)
509
Joe Gregorio9086bd32013-06-14 16:32:05 -0400510 def test_media_io_base_download_retries_5xx(self):
511 self.request.http = HttpMockSequence([
512 ({'status': '500'}, ''),
513 ({'status': '500'}, ''),
514 ({'status': '500'}, ''),
515 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800516 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400517 ({'status': '503'}, ''),
518 ({'status': '503'}, ''),
519 ({'status': '503'}, ''),
520 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800521 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400522 ])
523
524 download = MediaIoBaseDownload(
525 fd=self.fd, request=self.request, chunksize=3)
526
527 self.assertEqual(self.fd, download._fd)
528 self.assertEqual(3, download._chunksize)
529 self.assertEqual(0, download._progress)
530 self.assertEqual(None, download._total_size)
531 self.assertEqual(False, download._done)
532 self.assertEqual(self.request.uri, download._uri)
533
534 # Set time.sleep and random.random stubs.
535 sleeptimes = []
536 download._sleep = lambda x: sleeptimes.append(x)
537 download._rand = lambda: 10
538
539 status, done = download.next_chunk(num_retries=3)
540
541 # Check for exponential backoff using the rand function above.
542 self.assertEqual([20, 40, 80], sleeptimes)
543
Pat Ferate2b140222015-03-03 18:05:11 -0800544 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400545 self.assertEqual(False, done)
546 self.assertEqual(3, download._progress)
547 self.assertEqual(5, download._total_size)
548 self.assertEqual(3, status.resumable_progress)
549
550 # Reset time.sleep stub.
551 del sleeptimes[0:len(sleeptimes)]
552
553 status, done = download.next_chunk(num_retries=3)
554
555 # Check for exponential backoff using the rand function above.
556 self.assertEqual([20, 40, 80], sleeptimes)
557
Pat Ferate2b140222015-03-03 18:05:11 -0800558 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400559 self.assertEqual(True, done)
560 self.assertEqual(5, download._progress)
561 self.assertEqual(5, download._total_size)
562
andrewnestera4a44cf2017-03-31 16:09:31 +0300563 def test_media_io_base_download_empty_file(self):
564 self.request.http = HttpMockSequence([
565 ({'status': '200',
566 'content-range': '0-0/0'}, b''),
567 ])
568
569 download = MediaIoBaseDownload(
570 fd=self.fd, request=self.request, chunksize=3)
571
572 self.assertEqual(self.fd, download._fd)
573 self.assertEqual(0, download._progress)
574 self.assertEqual(None, download._total_size)
575 self.assertEqual(False, download._done)
576 self.assertEqual(self.request.uri, download._uri)
577
578 status, done = download.next_chunk()
579
580 self.assertEqual(True, done)
581 self.assertEqual(0, download._progress)
582 self.assertEqual(0, download._total_size)
583 self.assertEqual(0, status.progress())
584
Daniel44067782018-01-16 23:17:56 +0100585 def test_media_io_base_download_unknown_media_size(self):
586 self.request.http = HttpMockSequence([
587 ({'status': '200'}, b'123')
588 ])
589
590 download = MediaIoBaseDownload(
591 fd=self.fd, request=self.request, chunksize=3)
592
593 self.assertEqual(self.fd, download._fd)
594 self.assertEqual(0, download._progress)
595 self.assertEqual(None, download._total_size)
596 self.assertEqual(False, download._done)
597 self.assertEqual(self.request.uri, download._uri)
598
599 status, done = download.next_chunk()
600
601 self.assertEqual(self.fd.getvalue(), b'123')
602 self.assertEqual(True, done)
603 self.assertEqual(3, download._progress)
604 self.assertEqual(None, download._total_size)
605 self.assertEqual(0, status.progress())
606
607
Joe Gregorio66f57522011-11-30 11:00:00 -0500608EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
609Content-Type: application/json
610MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500611Host: www.googleapis.com
612content-length: 2\r\n\r\n{}"""
613
614
615NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
616Content-Type: application/json
617MIME-Version: 1.0
618Host: www.googleapis.com
619content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500620
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400621NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
622Content-Type: application/json
623MIME-Version: 1.0
624Host: www.googleapis.com\r\n\r\n"""
625
Joe Gregorio66f57522011-11-30 11:00:00 -0500626
627RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400628Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500629Content-Length: 14
630ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
631
632
INADA Naoki09157612015-03-25 01:51:03 +0900633BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500634Content-Type: application/http
635Content-Transfer-Encoding: binary
636Content-ID: <randomness+1>
637
638HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400639Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500640Content-Length: 14
641ETag: "etag/pony"\r\n\r\n{"foo": 42}
642
643--batch_foobarbaz
644Content-Type: application/http
645Content-Transfer-Encoding: binary
646Content-ID: <randomness+2>
647
648HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400649Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500650Content-Length: 14
651ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
652--batch_foobarbaz--"""
653
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500654
INADA Naoki09157612015-03-25 01:51:03 +0900655BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400656Content-Type: application/http
657Content-Transfer-Encoding: binary
658Content-ID: <randomness+1>
659
660HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400661Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400662Content-Length: 14
663ETag: "etag/pony"\r\n\r\n{"foo": 42}
664
665--batch_foobarbaz
666Content-Type: application/http
667Content-Transfer-Encoding: binary
668Content-ID: <randomness+2>
669
670HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400671Content-Type: application/json
672Content-Length: 245
673ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400674 "error": {
675 "errors": [
676 {
677 "domain": "usageLimits",
678 "reason": "accessNotConfigured",
679 "message": "Access Not Configured",
680 "debugInfo": "QuotaState: BLOCKED"
681 }
682 ],
683 "code": 403,
684 "message": "Access Not Configured"
685 }
686}
687
688--batch_foobarbaz--"""
689
690
INADA Naoki09157612015-03-25 01:51:03 +0900691BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500692Content-Type: application/http
693Content-Transfer-Encoding: binary
694Content-ID: <randomness+1>
695
Joe Gregorioc752e332012-07-11 14:43:52 -0400696HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400697Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500698Content-Length: 14
699ETag: "etag/pony"\r\n\r\n{"error": {"message":
700 "Authorizaton failed."}}
701
702--batch_foobarbaz
703Content-Type: application/http
704Content-Transfer-Encoding: binary
705Content-ID: <randomness+2>
706
707HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400708Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500709Content-Length: 14
710ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
711--batch_foobarbaz--"""
712
713
INADA Naoki09157612015-03-25 01:51:03 +0900714BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500715Content-Type: application/http
716Content-Transfer-Encoding: binary
717Content-ID: <randomness+1>
718
719HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400720Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500721Content-Length: 14
722ETag: "etag/pony"\r\n\r\n{"foo": 42}
723--batch_foobarbaz--"""
724
eesheeshc6425a02016-02-12 15:07:06 +0000725
726USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
727 "error": {
728 "errors": [
729 {
730 "domain": "usageLimits",
731 "reason": "userRateLimitExceeded",
732 "message": "User Rate Limit Exceeded"
733 }
734 ],
735 "code": 403,
736 "message": "User Rate Limit Exceeded"
737 }
738}"""
739
740
741RATE_LIMIT_EXCEEDED_RESPONSE = """{
742 "error": {
743 "errors": [
744 {
745 "domain": "usageLimits",
746 "reason": "rateLimitExceeded",
747 "message": "Rate Limit Exceeded"
748 }
749 ],
750 "code": 403,
751 "message": "Rate Limit Exceeded"
752 }
753}"""
754
755
756NOT_CONFIGURED_RESPONSE = """{
757 "error": {
758 "errors": [
759 {
760 "domain": "usageLimits",
761 "reason": "accessNotConfigured",
762 "message": "Access Not Configured"
763 }
764 ],
765 "code": 403,
766 "message": "Access Not Configured"
767 }
768}"""
769
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800770LIST_NOT_CONFIGURED_RESPONSE = """[
771 "error": {
772 "errors": [
773 {
774 "domain": "usageLimits",
775 "reason": "accessNotConfigured",
776 "message": "Access Not Configured"
777 }
778 ],
779 "code": 403,
780 "message": "Access Not Configured"
781 }
782]"""
783
Joe Gregorio654f4a22012-02-09 14:15:44 -0500784class Callbacks(object):
785 def __init__(self):
786 self.responses = {}
787 self.exceptions = {}
788
789 def f(self, request_id, response, exception):
790 self.responses[request_id] = response
791 self.exceptions[request_id] = exception
792
793
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500794class TestHttpRequest(unittest.TestCase):
795 def test_unicode(self):
796 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
797 model = JsonModel()
798 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
799 method = u'POST'
800 request = HttpRequest(
801 http,
802 model.response,
803 uri,
804 method=method,
805 body=u'{}',
806 headers={'content-type': 'application/json'})
807 request.execute()
808 self.assertEqual(uri, http.uri)
809 self.assertEqual(str, type(http.uri))
810 self.assertEqual(method, http.method)
811 self.assertEqual(str, type(http.method))
812
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100813 def test_empty_content_type(self):
814 """Test for #284"""
815 http = HttpMock(None, headers={'status': 200})
816 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
817 method = u'POST'
818 request = HttpRequest(
819 http,
820 _postproc_none,
821 uri,
822 method=method,
823 headers={'content-type': ''})
824 request.execute()
825 self.assertEqual('', http.headers.get('content-type'))
826
eesheeshc6425a02016-02-12 15:07:06 +0000827 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100828 model = JsonModel()
829 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000830 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
831 model.response,
832 u'https://www.example.com/json_api_endpoint')
833 request._sleep = lambda _x: 0 # do nothing
834 request._rand = lambda: 10
835 with self.assertRaises(socket.error):
836 response = request.execute(num_retries=3)
837
838
839 def test_retry_connection_errors_non_resumable(self):
840 model = JsonModel()
841 request = HttpRequest(
842 HttpMockWithErrors(3, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100843 model.response,
844 u'https://www.example.com/json_api_endpoint')
845 request._sleep = lambda _x: 0 # do nothing
846 request._rand = lambda: 10
847 response = request.execute(num_retries=3)
848 self.assertEqual({u'foo': u'bar'}, response)
849
eesheeshc6425a02016-02-12 15:07:06 +0000850 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100851 with open(datafile('small.png'), 'rb') as small_png_file:
852 small_png_fd = BytesIO(small_png_file.read())
853 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
854 chunksize=500, resumable=True)
855 model = JsonModel()
856
857 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000858 HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100859 3, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
860 model.response,
861 u'https://www.example.com/file_upload',
862 method='POST',
863 resumable=upload)
864 request._sleep = lambda _x: 0 # do nothing
865 request._rand = lambda: 10
866 response = request.execute(num_retries=3)
867 self.assertEqual({u'foo': u'bar'}, response)
868
Joe Gregorio9086bd32013-06-14 16:32:05 -0400869 def test_retry(self):
870 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000871 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
872 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
873 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
874 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400875 resp_seq.append(({'status': '200'}, '{}'))
876
877 http = HttpMockSequence(resp_seq)
878 model = JsonModel()
879 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
880 method = u'POST'
881 request = HttpRequest(
882 http,
883 model.response,
884 uri,
885 method=method,
886 body=u'{}',
887 headers={'content-type': 'application/json'})
888
889 sleeptimes = []
890 request._sleep = lambda x: sleeptimes.append(x)
891 request._rand = lambda: 10
892
893 request.execute(num_retries=num_retries)
894
895 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900896 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400897 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
898
eesheeshc6425a02016-02-12 15:07:06 +0000899 def test_no_retry_succeeds(self):
900 num_retries = 5
901 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
902
903 http = HttpMockSequence(resp_seq)
904 model = JsonModel()
905 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
906 method = u'POST'
907 request = HttpRequest(
908 http,
909 model.response,
910 uri,
911 method=method,
912 body=u'{}',
913 headers={'content-type': 'application/json'})
914
915 sleeptimes = []
916 request._sleep = lambda x: sleeptimes.append(x)
917 request._rand = lambda: 10
918
919 request.execute(num_retries=num_retries)
920
921 self.assertEqual(0, len(sleeptimes))
922
Joe Gregorio9086bd32013-06-14 16:32:05 -0400923 def test_no_retry_fails_fast(self):
924 http = HttpMockSequence([
925 ({'status': '500'}, ''),
926 ({'status': '200'}, '{}')
927 ])
928 model = JsonModel()
929 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
930 method = u'POST'
931 request = HttpRequest(
932 http,
933 model.response,
934 uri,
935 method=method,
936 body=u'{}',
937 headers={'content-type': 'application/json'})
938
939 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000940 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400941
eesheeshc6425a02016-02-12 15:07:06 +0000942 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400943 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000944 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400945
eesheeshc6425a02016-02-12 15:07:06 +0000946 def test_no_retry_403_not_configured_fails_fast(self):
947 http = HttpMockSequence([
948 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
949 ({'status': '200'}, '{}')
950 ])
951 model = JsonModel()
952 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
953 method = u'POST'
954 request = HttpRequest(
955 http,
956 model.response,
957 uri,
958 method=method,
959 body=u'{}',
960 headers={'content-type': 'application/json'})
961
962 request._rand = lambda: 1.0
963 request._sleep = mock.MagicMock()
964
965 with self.assertRaises(HttpError):
966 request.execute()
967 request._sleep.assert_not_called()
968
969 def test_no_retry_403_fails_fast(self):
970 http = HttpMockSequence([
971 ({'status': '403'}, ''),
972 ({'status': '200'}, '{}')
973 ])
974 model = JsonModel()
975 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
976 method = u'POST'
977 request = HttpRequest(
978 http,
979 model.response,
980 uri,
981 method=method,
982 body=u'{}',
983 headers={'content-type': 'application/json'})
984
985 request._rand = lambda: 1.0
986 request._sleep = mock.MagicMock()
987
988 with self.assertRaises(HttpError):
989 request.execute()
990 request._sleep.assert_not_called()
991
992 def test_no_retry_401_fails_fast(self):
993 http = HttpMockSequence([
994 ({'status': '401'}, ''),
995 ({'status': '200'}, '{}')
996 ])
997 model = JsonModel()
998 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
999 method = u'POST'
1000 request = HttpRequest(
1001 http,
1002 model.response,
1003 uri,
1004 method=method,
1005 body=u'{}',
1006 headers={'content-type': 'application/json'})
1007
1008 request._rand = lambda: 1.0
1009 request._sleep = mock.MagicMock()
1010
1011 with self.assertRaises(HttpError):
1012 request.execute()
1013 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001014
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001015 def test_no_retry_403_list_fails(self):
1016 http = HttpMockSequence([
1017 ({'status': '403'}, LIST_NOT_CONFIGURED_RESPONSE),
1018 ({'status': '200'}, '{}')
1019 ])
1020 model = JsonModel()
1021 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1022 method = u'POST'
1023 request = HttpRequest(
1024 http,
1025 model.response,
1026 uri,
1027 method=method,
1028 body=u'{}',
1029 headers={'content-type': 'application/json'})
1030
1031 request._rand = lambda: 1.0
1032 request._sleep = mock.MagicMock()
1033
1034 with self.assertRaises(HttpError):
1035 request.execute()
1036 request._sleep.assert_not_called()
1037
Joe Gregorio66f57522011-11-30 11:00:00 -05001038class TestBatch(unittest.TestCase):
1039
1040 def setUp(self):
1041 model = JsonModel()
1042 self.request1 = HttpRequest(
1043 None,
1044 model.response,
1045 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1046 method='POST',
1047 body='{}',
1048 headers={'content-type': 'application/json'})
1049
1050 self.request2 = HttpRequest(
1051 None,
1052 model.response,
1053 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001054 method='GET',
1055 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -05001056 headers={'content-type': 'application/json'})
1057
1058
1059 def test_id_to_from_content_id_header(self):
1060 batch = BatchHttpRequest()
1061 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
1062
1063 def test_invalid_content_id_header(self):
1064 batch = BatchHttpRequest()
1065 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
1066 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
1067 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
1068
1069 def test_serialize_request(self):
1070 batch = BatchHttpRequest()
1071 request = HttpRequest(
1072 None,
1073 None,
1074 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1075 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001076 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -05001077 headers={'content-type': 'application/json'},
1078 methodId=None,
1079 resumable=None)
1080 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001081 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001082
Joe Gregoriodd813822012-01-25 10:32:47 -05001083 def test_serialize_request_media_body(self):
1084 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001085 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001086 body = f.read()
1087 f.close()
1088
1089 request = HttpRequest(
1090 None,
1091 None,
1092 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1093 method='POST',
1094 body=body,
1095 headers={'content-type': 'application/json'},
1096 methodId=None,
1097 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001098 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001099 s = batch._serialize_request(request).splitlines()
1100
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001101 def test_serialize_request_no_body(self):
1102 batch = BatchHttpRequest()
1103 request = HttpRequest(
1104 None,
1105 None,
1106 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1107 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001108 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001109 headers={'content-type': 'application/json'},
1110 methodId=None,
1111 resumable=None)
1112 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001113 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001114
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001115 def test_serialize_get_request_no_body(self):
1116 batch = BatchHttpRequest()
1117 request = HttpRequest(
1118 None,
1119 None,
1120 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1121 method='GET',
1122 body=None,
1123 headers={'content-type': 'application/json'},
1124 methodId=None,
1125 resumable=None)
1126 s = batch._serialize_request(request).splitlines()
1127 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1128
Joe Gregorio66f57522011-11-30 11:00:00 -05001129 def test_deserialize_response(self):
1130 batch = BatchHttpRequest()
1131 resp, content = batch._deserialize_response(RESPONSE)
1132
Joe Gregorio654f4a22012-02-09 14:15:44 -05001133 self.assertEqual(200, resp.status)
1134 self.assertEqual('OK', resp.reason)
1135 self.assertEqual(11, resp.version)
1136 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001137
1138 def test_new_id(self):
1139 batch = BatchHttpRequest()
1140
1141 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001142 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001143
1144 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001145 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001146
1147 batch.add(self.request1, request_id='3')
1148
1149 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001150 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001151
1152 def test_add(self):
1153 batch = BatchHttpRequest()
1154 batch.add(self.request1, request_id='1')
1155 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1156
1157 def test_add_fail_for_resumable(self):
1158 batch = BatchHttpRequest()
1159
1160 upload = MediaFileUpload(
1161 datafile('small.png'), chunksize=500, resumable=True)
1162 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001163 with self.assertRaises(BatchError) as batch_error:
1164 batch.add(self.request1, request_id='1')
1165 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001166
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001167 def test_execute_empty_batch_no_http(self):
1168 batch = BatchHttpRequest()
1169 ret = batch.execute()
1170 self.assertEqual(None, ret)
1171
Joe Gregorio66f57522011-11-30 11:00:00 -05001172 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001173 batch = BatchHttpRequest()
1174 callbacks = Callbacks()
1175
1176 batch.add(self.request1, callback=callbacks.f)
1177 batch.add(self.request2, callback=callbacks.f)
1178 http = HttpMockSequence([
1179 ({'status': '200',
1180 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1181 BATCH_RESPONSE),
1182 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001183 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001184 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1185 self.assertEqual(None, callbacks.exceptions['1'])
1186 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1187 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001188
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001189 def test_execute_request_body(self):
1190 batch = BatchHttpRequest()
1191
1192 batch.add(self.request1)
1193 batch.add(self.request2)
1194 http = HttpMockSequence([
1195 ({'status': '200',
1196 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1197 'echo_request_body'),
1198 ])
1199 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001200 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001201 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001202 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001203 boundary, _ = e.content.split(None, 1)
1204 self.assertEqual('--', boundary[:2])
1205 parts = e.content.split(boundary)
1206 self.assertEqual(4, len(parts))
1207 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001208 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001209 header = parts[1].splitlines()[1]
1210 self.assertEqual('Content-Type: application/http', header)
1211
Gabriel Garcia23174be2016-05-25 17:28:07 +02001212 def test_execute_initial_refresh_oauth2(self):
1213 batch = BatchHttpRequest()
1214 callbacks = Callbacks()
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -07001215 cred = MockCredentials('Foo', expired=True)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001216
1217 http = HttpMockSequence([
1218 ({'status': '200',
1219 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1220 BATCH_SINGLE_RESPONSE),
1221 ])
1222
1223 cred.authorize(http)
1224
1225 batch.add(self.request1, callback=callbacks.f)
1226 batch.execute(http=http)
1227
1228 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1229 self.assertIsNone(callbacks.exceptions['1'])
1230
1231 self.assertEqual(1, cred._refreshed)
1232
1233 self.assertEqual(1, cred._authorized)
1234
1235 self.assertEqual(1, cred._applied)
1236
Joe Gregorio654f4a22012-02-09 14:15:44 -05001237 def test_execute_refresh_and_retry_on_401(self):
1238 batch = BatchHttpRequest()
1239 callbacks = Callbacks()
1240 cred_1 = MockCredentials('Foo')
1241 cred_2 = MockCredentials('Bar')
1242
1243 http = HttpMockSequence([
1244 ({'status': '200',
1245 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1246 BATCH_RESPONSE_WITH_401),
1247 ({'status': '200',
1248 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1249 BATCH_SINGLE_RESPONSE),
1250 ])
1251
1252 creds_http_1 = HttpMockSequence([])
1253 cred_1.authorize(creds_http_1)
1254
1255 creds_http_2 = HttpMockSequence([])
1256 cred_2.authorize(creds_http_2)
1257
1258 self.request1.http = creds_http_1
1259 self.request2.http = creds_http_2
1260
1261 batch.add(self.request1, callback=callbacks.f)
1262 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001263 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001264
1265 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1266 self.assertEqual(None, callbacks.exceptions['1'])
1267 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1268 self.assertEqual(None, callbacks.exceptions['2'])
1269
1270 self.assertEqual(1, cred_1._refreshed)
1271 self.assertEqual(0, cred_2._refreshed)
1272
1273 self.assertEqual(1, cred_1._authorized)
1274 self.assertEqual(1, cred_2._authorized)
1275
1276 self.assertEqual(1, cred_2._applied)
1277 self.assertEqual(2, cred_1._applied)
1278
1279 def test_http_errors_passed_to_callback(self):
1280 batch = BatchHttpRequest()
1281 callbacks = Callbacks()
1282 cred_1 = MockCredentials('Foo')
1283 cred_2 = MockCredentials('Bar')
1284
1285 http = HttpMockSequence([
1286 ({'status': '200',
1287 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1288 BATCH_RESPONSE_WITH_401),
1289 ({'status': '200',
1290 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1291 BATCH_RESPONSE_WITH_401),
1292 ])
1293
1294 creds_http_1 = HttpMockSequence([])
1295 cred_1.authorize(creds_http_1)
1296
1297 creds_http_2 = HttpMockSequence([])
1298 cred_2.authorize(creds_http_2)
1299
1300 self.request1.http = creds_http_1
1301 self.request2.http = creds_http_2
1302
1303 batch.add(self.request1, callback=callbacks.f)
1304 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001305 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001306
1307 self.assertEqual(None, callbacks.responses['1'])
1308 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001309 self.assertEqual(
1310 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001311 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1312 self.assertEqual(None, callbacks.exceptions['2'])
1313
Joe Gregorio66f57522011-11-30 11:00:00 -05001314 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001315 callbacks = Callbacks()
1316 batch = BatchHttpRequest(callback=callbacks.f)
1317
1318 batch.add(self.request1)
1319 batch.add(self.request2)
1320 http = HttpMockSequence([
1321 ({'status': '200',
1322 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1323 BATCH_RESPONSE),
1324 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001325 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001326 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1327 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001328
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001329 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001330 callbacks = Callbacks()
1331 batch = BatchHttpRequest(callback=callbacks.f)
1332
1333 batch.add(self.request1)
1334 batch.add(self.request2)
1335 http = HttpMockSequence([
1336 ({'status': '200',
1337 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1338 BATCH_ERROR_RESPONSE),
1339 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001340 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001341 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1342 expected = ('<HttpError 403 when requesting '
1343 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1344 '"Access Not Configured">')
1345 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001346
Joe Gregorio5c120db2012-08-23 09:13:55 -04001347
Joe Gregorioba5c7902012-08-03 12:48:16 -04001348class TestRequestUriTooLong(unittest.TestCase):
1349
1350 def test_turn_get_into_post(self):
1351
1352 def _postproc(resp, content):
1353 return content
1354
1355 http = HttpMockSequence([
1356 ({'status': '200'},
1357 'echo_request_body'),
1358 ({'status': '200'},
1359 'echo_request_headers'),
1360 ])
1361
1362 # Send a long query parameter.
1363 query = {
1364 'q': 'a' * MAX_URI_LENGTH + '?&'
1365 }
1366 req = HttpRequest(
1367 http,
1368 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001369 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001370 method='GET',
1371 body=None,
1372 headers={},
1373 methodId='foo',
1374 resumable=None)
1375
1376 # Query parameters should be sent in the body.
1377 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001378 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001379
1380 # Extra headers should be set.
1381 response = req.execute()
1382 self.assertEqual('GET', response['x-http-method-override'])
1383 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1384 self.assertEqual(
1385 'application/x-www-form-urlencoded', response['content-type'])
1386
Joe Gregorio5c120db2012-08-23 09:13:55 -04001387
1388class TestStreamSlice(unittest.TestCase):
1389 """Test _StreamSlice."""
1390
1391 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001392 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001393
1394 def test_read(self):
1395 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001396 self.assertEqual(b'', s.read(0))
1397 self.assertEqual(b'0', s.read(1))
1398 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001399
1400 def test_read_too_much(self):
1401 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001402 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001403
1404 def test_read_all(self):
1405 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001406 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001407
Ali Afshar164f37e2013-01-07 14:05:45 -08001408
1409class TestResponseCallback(unittest.TestCase):
1410 """Test adding callbacks to responses."""
1411
1412 def test_ensure_response_callback(self):
1413 m = JsonModel()
1414 request = HttpRequest(
1415 None,
1416 m.response,
1417 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1418 method='POST',
1419 body='{}',
1420 headers={'content-type': 'application/json'})
1421 h = HttpMockSequence([ ({'status': 200}, '{}')])
1422 responses = []
1423 def _on_response(resp, responses=responses):
1424 responses.append(resp)
1425 request.add_response_callback(_on_response)
1426 request.execute(http=h)
1427 self.assertEqual(1, len(responses))
1428
1429
Craig Gurnik8e55b762015-01-20 15:00:10 -05001430class TestHttpMock(unittest.TestCase):
1431 def test_default_response_headers(self):
1432 http = HttpMock(datafile('zoo.json'))
1433 resp, content = http.request("http://example.com")
1434 self.assertEqual(resp.status, 200)
1435
Alan Briolat26b01002015-08-14 00:13:57 +01001436 def test_error_response(self):
1437 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1438 model = JsonModel()
1439 request = HttpRequest(
1440 http,
1441 model.response,
1442 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1443 method='GET',
1444 headers={})
1445 self.assertRaises(HttpError, request.execute)
1446
Craig Gurnik8e55b762015-01-20 15:00:10 -05001447
Igor Maravić22435292017-01-19 22:28:22 +01001448class TestHttpBuild(unittest.TestCase):
1449 original_socket_default_timeout = None
1450
1451 @classmethod
1452 def setUpClass(cls):
1453 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1454
1455 @classmethod
1456 def tearDownClass(cls):
1457 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1458
1459 def test_build_http_sets_default_timeout_if_none_specified(self):
1460 socket.setdefaulttimeout(None)
1461 http = build_http()
1462 self.assertIsInstance(http.timeout, int)
1463 self.assertGreater(http.timeout, 0)
1464
1465 def test_build_http_default_timeout_can_be_overridden(self):
1466 socket.setdefaulttimeout(1.5)
1467 http = build_http()
1468 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1469
1470 def test_build_http_default_timeout_can_be_set_to_zero(self):
1471 socket.setdefaulttimeout(0)
1472 http = build_http()
1473 self.assertEquals(http.timeout, 0)
1474
1475
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001476if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001477 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001478 unittest.main()