blob: b92e63fcd5c779c314b0f3d7693291783f05d7bc [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
Chris McDonough0dc81bf2018-07-19 11:19:58 -040032import json
Joe Gregorio7cbceab2011-06-27 10:46:54 -040033import httplib2
Xiaofei Wang20b67582019-07-17 11:16:53 -070034import io
Joe Gregorio9086bd32013-06-14 16:32:05 -040035import logging
eesheeshc6425a02016-02-12 15:07:06 +000036import mock
Joe Gregoriod0bd3882011-11-22 09:49:47 -050037import os
Pat Ferate497a90f2015-03-09 09:52:54 -070038import unittest2 as unittest
Joe Gregorio9086bd32013-06-14 16:32:05 -040039import random
eesheeshc6425a02016-02-12 15:07:06 +000040import socket
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +010041import ssl
Joe Gregorio9086bd32013-06-14 16:32:05 -040042import time
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050043
John Asmuth864311d2014-04-24 15:46:08 -040044from googleapiclient.discovery import build
45from googleapiclient.errors import BatchError
46from googleapiclient.errors import HttpError
47from googleapiclient.errors import InvalidChunkSizeError
Igor Maravić22435292017-01-19 22:28:22 +010048from googleapiclient.http import build_http
John Asmuth864311d2014-04-24 15:46:08 -040049from googleapiclient.http import BatchHttpRequest
50from googleapiclient.http import HttpMock
51from googleapiclient.http import HttpMockSequence
52from googleapiclient.http import HttpRequest
53from googleapiclient.http import MAX_URI_LENGTH
54from googleapiclient.http import MediaFileUpload
55from googleapiclient.http import MediaInMemoryUpload
56from googleapiclient.http import MediaIoBaseDownload
57from googleapiclient.http import MediaIoBaseUpload
58from googleapiclient.http import MediaUpload
59from googleapiclient.http import _StreamSlice
60from googleapiclient.http import set_user_agent
61from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050062from oauth2client.client import Credentials
63
64
65class MockCredentials(Credentials):
66 """Mock class for all Credentials objects."""
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070067 def __init__(self, bearer_token, expired=False):
Joe Gregorio654f4a22012-02-09 14:15:44 -050068 super(MockCredentials, self).__init__()
69 self._authorized = 0
70 self._refreshed = 0
71 self._applied = 0
72 self._bearer_token = bearer_token
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070073 self._access_token_expired = expired
74
75 @property
Jon Wayne Parrott20e61352018-01-18 09:16:37 -080076 def access_token(self):
77 return self._bearer_token
78
79 @property
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070080 def access_token_expired(self):
81 return self._access_token_expired
Joe Gregorio654f4a22012-02-09 14:15:44 -050082
83 def authorize(self, http):
84 self._authorized += 1
85
86 request_orig = http.request
87
88 # The closure that will replace 'httplib2.Http.request'.
89 def new_request(uri, method='GET', body=None, headers=None,
90 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
91 connection_type=None):
92 # Modify the request headers to add the appropriate
93 # Authorization header.
94 if headers is None:
95 headers = {}
96 self.apply(headers)
97
98 resp, content = request_orig(uri, method, body, headers,
99 redirections, connection_type)
100
101 return resp, content
102
103 # Replace the request method with our own closure.
104 http.request = new_request
105
106 # Set credentials as a property of the request method.
107 setattr(http.request, 'credentials', self)
108
109 return http
110
111 def refresh(self, http):
112 self._refreshed += 1
113
114 def apply(self, headers):
115 self._applied += 1
116 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500117
118
eesheeshc6425a02016-02-12 15:07:06 +0000119class HttpMockWithErrors(object):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100120 def __init__(self, num_errors, success_json, success_data):
121 self.num_errors = num_errors
122 self.success_json = success_json
123 self.success_data = success_data
124
125 def request(self, *args, **kwargs):
126 if not self.num_errors:
127 return httplib2.Response(self.success_json), self.success_data
128 else:
129 self.num_errors -= 1
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700130 if self.num_errors == 1: # initial == 2
eesheeshc6425a02016-02-12 15:07:06 +0000131 raise ssl.SSLError()
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400132 if self.num_errors == 3: # initial == 4
133 raise httplib2.ServerNotFoundError()
134 else: # initial != 2,4
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200135 if self.num_errors == 2:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700136 # first try a broken pipe error (#218)
137 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200138 ex.errno = socket.errno.EPIPE
139 else:
140 # Initialize the timeout error code to the platform's error code.
141 try:
142 # For Windows:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700143 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200144 ex.errno = socket.errno.WSAETIMEDOUT
145 except AttributeError:
146 # For Linux/Mac:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700147 if PY3:
148 ex = socket.timeout()
149 else:
150 ex = socket.error()
151 ex.errno = socket.errno.ETIMEDOUT
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200152 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000153 raise ex
154
155
156class HttpMockWithNonRetriableErrors(object):
157 def __init__(self, num_errors, success_json, success_data):
158 self.num_errors = num_errors
159 self.success_json = success_json
160 self.success_data = success_data
161
162 def request(self, *args, **kwargs):
163 if not self.num_errors:
164 return httplib2.Response(self.success_json), self.success_data
165 else:
166 self.num_errors -= 1
167 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200168 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000169 try:
170 # For Windows:
171 ex.errno = socket.errno.WSAECONNREFUSED
172 except AttributeError:
173 # For Linux/Mac:
174 ex.errno = socket.errno.ECONNREFUSED
175 # Now raise the correct timeout error.
176 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100177
178
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500179DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
180
181
182def datafile(filename):
183 return os.path.join(DATA_DIR, filename)
184
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100185def _postproc_none(*kwargs):
186 pass
187
188
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500189class TestUserAgent(unittest.TestCase):
190
191 def test_set_user_agent(self):
192 http = HttpMockSequence([
193 ({'status': '200'}, 'echo_request_headers'),
194 ])
195
196 http = set_user_agent(http, "my_app/5.5")
197 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500198 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500199
200 def test_set_user_agent_nested(self):
201 http = HttpMockSequence([
202 ({'status': '200'}, 'echo_request_headers'),
203 ])
204
205 http = set_user_agent(http, "my_app/5.5")
206 http = set_user_agent(http, "my_library/0.1")
207 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500208 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500209
Joe Gregorio910b9b12012-06-12 09:36:30 -0400210
211class TestMediaUpload(unittest.TestCase):
212
Xiaofei Wang20b67582019-07-17 11:16:53 -0700213 def test_media_file_upload_closes_fd_in___del__(self):
214 file_desc = mock.Mock(spec=io.TextIOWrapper)
215 opener = mock.mock_open(file_desc)
Bu Sun Kim0ba1ca42019-07-23 14:42:53 -0700216 if PY3:
217 with mock.patch('builtins.open', return_value=opener):
218 upload = MediaFileUpload(datafile('test_close'), mimetype='text/plain')
219 else:
220 with mock.patch('__builtin__.open', return_value=opener):
221 upload = MediaFileUpload(datafile('test_close'), mimetype='text/plain')
Xiaofei Wang20b67582019-07-17 11:16:53 -0700222 self.assertIs(upload.stream(), file_desc)
223 del upload
224 file_desc.close.assert_called_once_with()
225
Nam T. Nguyendc136312015-12-01 10:18:56 -0800226 def test_media_file_upload_mimetype_detection(self):
227 upload = MediaFileUpload(datafile('small.png'))
228 self.assertEqual('image/png', upload.mimetype())
229
230 upload = MediaFileUpload(datafile('empty'))
231 self.assertEqual('application/octet-stream', upload.mimetype())
232
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500233 def test_media_file_upload_to_from_json(self):
234 upload = MediaFileUpload(
235 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500236 self.assertEqual('image/png', upload.mimetype())
237 self.assertEqual(190, upload.size())
238 self.assertEqual(True, upload.resumable())
239 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800240 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500241
242 json = upload.to_json()
243 new_upload = MediaUpload.new_from_json(json)
244
Joe Gregorio654f4a22012-02-09 14:15:44 -0500245 self.assertEqual('image/png', new_upload.mimetype())
246 self.assertEqual(190, new_upload.size())
247 self.assertEqual(True, new_upload.resumable())
248 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800249 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500250
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400251 def test_media_file_upload_raises_on_invalid_chunksize(self):
252 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
253 datafile('small.png'), mimetype='image/png', chunksize=-2,
254 resumable=True)
255
Ali Afshar1cb6b672012-03-12 08:46:14 -0400256 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800257 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400258 resumable=True)
259 self.assertEqual('text/plain', media.mimetype())
260 self.assertEqual(10, media.chunksize())
261 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800262 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400263 self.assertEqual(6, media.size())
264
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500265 def test_http_request_to_from_json(self):
Igor Maravić22435292017-01-19 22:28:22 +0100266 http = build_http()
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500267 media_upload = MediaFileUpload(
268 datafile('small.png'), chunksize=500, resumable=True)
269 req = HttpRequest(
270 http,
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100271 _postproc_none,
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500272 'http://example.com',
273 method='POST',
274 body='{}',
275 headers={'content-type': 'multipart/related; boundary="---flubber"'},
276 methodId='foo',
277 resumable=media_upload)
278
279 json = req.to_json()
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100280 new_req = HttpRequest.from_json(json, http, _postproc_none)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500281
Joe Gregorio654f4a22012-02-09 14:15:44 -0500282 self.assertEqual({'content-type':
283 'multipart/related; boundary="---flubber"'},
284 new_req.headers)
285 self.assertEqual('http://example.com', new_req.uri)
286 self.assertEqual('{}', new_req.body)
287 self.assertEqual(http, new_req.http)
288 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500289
Joe Gregorio9086bd32013-06-14 16:32:05 -0400290 self.assertEqual(random.random, new_req._rand)
291 self.assertEqual(time.sleep, new_req._sleep)
292
Joe Gregorio910b9b12012-06-12 09:36:30 -0400293
294class TestMediaIoBaseUpload(unittest.TestCase):
295
296 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800297 fd = FileIO(datafile('small.png'), 'r')
298 upload = MediaIoBaseUpload(
299 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
300 self.assertEqual('image/png', upload.mimetype())
301 self.assertEqual(190, upload.size())
302 self.assertEqual(True, upload.resumable())
303 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800304 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400305
306 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800307 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400308 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400309 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400310 self.assertEqual('image/png', upload.mimetype())
311 self.assertEqual(190, upload.size())
312 self.assertEqual(True, upload.resumable())
313 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800314 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400315 f.close()
316
317 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800318 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400319 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400320
321 try:
322 json = upload.to_json()
323 self.fail('MediaIoBaseUpload should not be serializable.')
324 except NotImplementedError:
325 pass
326
Pat Feratec6050872015-03-03 18:24:59 -0800327 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400328 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800329 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800330 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400331 f.close()
332
333 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400334 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400335 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400336 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400337 self.assertEqual(True, upload.resumable())
338 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800339 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400340 f.close()
341
342 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800343 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800344 fd = BytesIO(f.read())
345 upload = MediaIoBaseUpload(
346 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
347 self.assertEqual('image/png', upload.mimetype())
348 self.assertEqual(190, upload.size())
349 self.assertEqual(True, upload.resumable())
350 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800351 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400352
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400353 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800354 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800355 fd = BytesIO(f.read())
356 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
357 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400358
359 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800360 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800361 upload = MediaIoBaseUpload(
362 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
363 self.assertEqual(True, upload.has_stream())
364 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400365
Joe Gregorio9086bd32013-06-14 16:32:05 -0400366 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800367 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800368 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400369 upload = MediaIoBaseUpload(
370 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
371
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500372 # Simulate errors for both the request that creates the resumable upload
373 # and the upload itself.
Joe Gregorio9086bd32013-06-14 16:32:05 -0400374 http = HttpMockSequence([
375 ({'status': '500'}, ''),
376 ({'status': '500'}, ''),
377 ({'status': '503'}, ''),
378 ({'status': '200', 'location': 'location'}, ''),
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500379 ({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
380 ({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE),
381 ({'status': '429'}, ''),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400382 ({'status': '200'}, '{}'),
383 ])
384
385 model = JsonModel()
386 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
387 method = u'POST'
388 request = HttpRequest(
389 http,
390 model.response,
391 uri,
392 method=method,
393 headers={},
394 resumable=upload)
395
396 sleeptimes = []
397 request._sleep = lambda x: sleeptimes.append(x)
398 request._rand = lambda: 10
399
400 request.execute(num_retries=3)
401 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
402
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500403 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
404 fd = BytesIO(b"i am png")
405 upload = MediaIoBaseUpload(
406 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
407
408 http = HttpMockSequence([
409 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
410 ({'status': '200'}, '{}')
411 ])
412
413 model = JsonModel()
414 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
415 method = u'POST'
416 request = HttpRequest(
417 http,
418 model.response,
419 uri,
420 method=method,
421 headers={},
422 resumable=upload)
423
424 request._rand = lambda: 1.0
425 request._sleep = mock.MagicMock()
426
427 with self.assertRaises(HttpError):
428 request.execute(num_retries=3)
429 request._sleep.assert_not_called()
430
Joe Gregorio910b9b12012-06-12 09:36:30 -0400431
Joe Gregorio708388c2012-06-15 13:43:04 -0400432class TestMediaIoBaseDownload(unittest.TestCase):
433
434 def setUp(self):
435 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400436 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400437 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800438 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400439
440 def test_media_io_base_download(self):
441 self.request.http = HttpMockSequence([
442 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800443 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400444 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800445 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400446 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400447 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400448
449 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400450 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400451
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400452 self.assertEqual(self.fd, download._fd)
453 self.assertEqual(3, download._chunksize)
454 self.assertEqual(0, download._progress)
455 self.assertEqual(None, download._total_size)
456 self.assertEqual(False, download._done)
457 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400458
459 status, done = download.next_chunk()
460
Pat Ferate2b140222015-03-03 18:05:11 -0800461 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400462 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400463 self.assertEqual(3, download._progress)
464 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400465 self.assertEqual(3, status.resumable_progress)
466
467 status, done = download.next_chunk()
468
Pat Ferate2b140222015-03-03 18:05:11 -0800469 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400470 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400471 self.assertEqual(5, download._progress)
472 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400473
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400474 def test_media_io_base_download_custom_request_headers(self):
475 self.request.http = HttpMockSequence([
476 ({'status': '200',
477 'content-range': '0-2/5'}, 'echo_request_headers_as_json'),
478 ({'status': '200',
479 'content-range': '3-4/5'}, 'echo_request_headers_as_json'),
480 ])
481 self.assertEqual(True, self.request.http.follow_redirects)
482
483 self.request.headers['Cache-Control'] = 'no-store'
484
485 download = MediaIoBaseDownload(
486 fd=self.fd, request=self.request, chunksize=3)
487
Bu Sun Kim07f647c2019-08-09 14:55:24 -0700488 self.assertEqual(download._headers.get('Cache-Control'), 'no-store')
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400489
490 status, done = download.next_chunk()
491
Bu Sun Kim07f647c2019-08-09 14:55:24 -0700492 result = json.loads(self.fd.getvalue().decode('utf-8'))
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400493
Bu Sun Kim07f647c2019-08-09 14:55:24 -0700494 # assert that that the header we added to the original request is
495 # sent up to the server on each call to next_chunk
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400496
Bu Sun Kim07f647c2019-08-09 14:55:24 -0700497 self.assertEqual(result.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400498
499 download._fd = self.fd = BytesIO()
500 status, done = download.next_chunk()
501
Bu Sun Kim07f647c2019-08-09 14:55:24 -0700502 result = json.loads(self.fd.getvalue().decode('utf-8'))
503 self.assertEqual(result.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400504
Joe Gregorio708388c2012-06-15 13:43:04 -0400505 def test_media_io_base_download_handle_redirects(self):
506 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400507 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800508 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400509 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800510 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400511 ])
512
513 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400514 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400515
516 status, done = download.next_chunk()
517
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400518 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400519
520 def test_media_io_base_download_handle_4xx(self):
521 self.request.http = HttpMockSequence([
522 ({'status': '400'}, ''),
523 ])
524
525 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400526 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400527
528 try:
529 status, done = download.next_chunk()
530 self.fail('Should raise an exception')
531 except HttpError:
532 pass
533
534 # Even after raising an exception we can pick up where we left off.
535 self.request.http = HttpMockSequence([
536 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800537 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400538 ])
539
540 status, done = download.next_chunk()
541
Pat Ferate2b140222015-03-03 18:05:11 -0800542 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400543
eesheeshc6425a02016-02-12 15:07:06 +0000544 def test_media_io_base_download_retries_connection_errors(self):
545 self.request.http = HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400546 4, {'status': '200', 'content-range': '0-2/3'}, b'123')
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100547
548 download = MediaIoBaseDownload(
549 fd=self.fd, request=self.request, chunksize=3)
550 download._sleep = lambda _x: 0 # do nothing
551 download._rand = lambda: 10
552
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400553 status, done = download.next_chunk(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100554
555 self.assertEqual(self.fd.getvalue(), b'123')
556 self.assertEqual(True, done)
557
Joe Gregorio9086bd32013-06-14 16:32:05 -0400558 def test_media_io_base_download_retries_5xx(self):
559 self.request.http = HttpMockSequence([
560 ({'status': '500'}, ''),
561 ({'status': '500'}, ''),
562 ({'status': '500'}, ''),
563 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800564 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400565 ({'status': '503'}, ''),
566 ({'status': '503'}, ''),
567 ({'status': '503'}, ''),
568 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800569 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400570 ])
571
572 download = MediaIoBaseDownload(
573 fd=self.fd, request=self.request, chunksize=3)
574
575 self.assertEqual(self.fd, download._fd)
576 self.assertEqual(3, download._chunksize)
577 self.assertEqual(0, download._progress)
578 self.assertEqual(None, download._total_size)
579 self.assertEqual(False, download._done)
580 self.assertEqual(self.request.uri, download._uri)
581
582 # Set time.sleep and random.random stubs.
583 sleeptimes = []
584 download._sleep = lambda x: sleeptimes.append(x)
585 download._rand = lambda: 10
586
587 status, done = download.next_chunk(num_retries=3)
588
589 # Check for exponential backoff using the rand function above.
590 self.assertEqual([20, 40, 80], sleeptimes)
591
Pat Ferate2b140222015-03-03 18:05:11 -0800592 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400593 self.assertEqual(False, done)
594 self.assertEqual(3, download._progress)
595 self.assertEqual(5, download._total_size)
596 self.assertEqual(3, status.resumable_progress)
597
598 # Reset time.sleep stub.
599 del sleeptimes[0:len(sleeptimes)]
600
601 status, done = download.next_chunk(num_retries=3)
602
603 # Check for exponential backoff using the rand function above.
604 self.assertEqual([20, 40, 80], sleeptimes)
605
Pat Ferate2b140222015-03-03 18:05:11 -0800606 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400607 self.assertEqual(True, done)
608 self.assertEqual(5, download._progress)
609 self.assertEqual(5, download._total_size)
610
andrewnestera4a44cf2017-03-31 16:09:31 +0300611 def test_media_io_base_download_empty_file(self):
612 self.request.http = HttpMockSequence([
613 ({'status': '200',
614 'content-range': '0-0/0'}, b''),
615 ])
616
617 download = MediaIoBaseDownload(
618 fd=self.fd, request=self.request, chunksize=3)
619
620 self.assertEqual(self.fd, download._fd)
621 self.assertEqual(0, download._progress)
622 self.assertEqual(None, download._total_size)
623 self.assertEqual(False, download._done)
624 self.assertEqual(self.request.uri, download._uri)
625
626 status, done = download.next_chunk()
627
628 self.assertEqual(True, done)
629 self.assertEqual(0, download._progress)
630 self.assertEqual(0, download._total_size)
631 self.assertEqual(0, status.progress())
632
Daniel44067782018-01-16 23:17:56 +0100633 def test_media_io_base_download_unknown_media_size(self):
634 self.request.http = HttpMockSequence([
635 ({'status': '200'}, b'123')
636 ])
637
638 download = MediaIoBaseDownload(
639 fd=self.fd, request=self.request, chunksize=3)
640
641 self.assertEqual(self.fd, download._fd)
642 self.assertEqual(0, download._progress)
643 self.assertEqual(None, download._total_size)
644 self.assertEqual(False, download._done)
645 self.assertEqual(self.request.uri, download._uri)
646
647 status, done = download.next_chunk()
648
649 self.assertEqual(self.fd.getvalue(), b'123')
650 self.assertEqual(True, done)
651 self.assertEqual(3, download._progress)
652 self.assertEqual(None, download._total_size)
653 self.assertEqual(0, status.progress())
654
655
Joe Gregorio66f57522011-11-30 11:00:00 -0500656EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
657Content-Type: application/json
658MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500659Host: www.googleapis.com
660content-length: 2\r\n\r\n{}"""
661
662
663NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
664Content-Type: application/json
665MIME-Version: 1.0
666Host: www.googleapis.com
667content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500668
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400669NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
670Content-Type: application/json
671MIME-Version: 1.0
672Host: www.googleapis.com\r\n\r\n"""
673
Joe Gregorio66f57522011-11-30 11:00:00 -0500674
675RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400676Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500677Content-Length: 14
678ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
679
680
INADA Naoki09157612015-03-25 01:51:03 +0900681BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500682Content-Type: application/http
683Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400684Content-ID: <randomness + 1>
Joe Gregorio66f57522011-11-30 11:00:00 -0500685
686HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400687Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500688Content-Length: 14
689ETag: "etag/pony"\r\n\r\n{"foo": 42}
690
691--batch_foobarbaz
692Content-Type: application/http
693Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400694Content-ID: <randomness + 2>
Joe Gregorio66f57522011-11-30 11:00:00 -0500695
696HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400697Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500698Content-Length: 14
699ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
700--batch_foobarbaz--"""
701
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500702
INADA Naoki09157612015-03-25 01:51:03 +0900703BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400704Content-Type: application/http
705Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400706Content-ID: <randomness + 1>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400707
708HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400709Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400710Content-Length: 14
711ETag: "etag/pony"\r\n\r\n{"foo": 42}
712
713--batch_foobarbaz
714Content-Type: application/http
715Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400716Content-ID: <randomness + 2>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400717
718HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400719Content-Type: application/json
720Content-Length: 245
721ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400722 "error": {
723 "errors": [
724 {
725 "domain": "usageLimits",
726 "reason": "accessNotConfigured",
727 "message": "Access Not Configured",
728 "debugInfo": "QuotaState: BLOCKED"
729 }
730 ],
731 "code": 403,
732 "message": "Access Not Configured"
733 }
734}
735
736--batch_foobarbaz--"""
737
738
INADA Naoki09157612015-03-25 01:51:03 +0900739BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500740Content-Type: application/http
741Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400742Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500743
Joe Gregorioc752e332012-07-11 14:43:52 -0400744HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400745Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500746Content-Length: 14
747ETag: "etag/pony"\r\n\r\n{"error": {"message":
748 "Authorizaton failed."}}
749
750--batch_foobarbaz
751Content-Type: application/http
752Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400753Content-ID: <randomness + 2>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500754
755HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400756Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500757Content-Length: 14
758ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
759--batch_foobarbaz--"""
760
761
INADA Naoki09157612015-03-25 01:51:03 +0900762BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500763Content-Type: application/http
764Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400765Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500766
767HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400768Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500769Content-Length: 14
770ETag: "etag/pony"\r\n\r\n{"foo": 42}
771--batch_foobarbaz--"""
772
eesheeshc6425a02016-02-12 15:07:06 +0000773
774USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
775 "error": {
776 "errors": [
777 {
778 "domain": "usageLimits",
779 "reason": "userRateLimitExceeded",
780 "message": "User Rate Limit Exceeded"
781 }
782 ],
783 "code": 403,
784 "message": "User Rate Limit Exceeded"
785 }
786}"""
787
788
789RATE_LIMIT_EXCEEDED_RESPONSE = """{
790 "error": {
791 "errors": [
792 {
793 "domain": "usageLimits",
794 "reason": "rateLimitExceeded",
795 "message": "Rate Limit Exceeded"
796 }
797 ],
798 "code": 403,
799 "message": "Rate Limit Exceeded"
800 }
801}"""
802
803
804NOT_CONFIGURED_RESPONSE = """{
805 "error": {
806 "errors": [
807 {
808 "domain": "usageLimits",
809 "reason": "accessNotConfigured",
810 "message": "Access Not Configured"
811 }
812 ],
813 "code": 403,
814 "message": "Access Not Configured"
815 }
816}"""
817
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800818LIST_NOT_CONFIGURED_RESPONSE = """[
819 "error": {
820 "errors": [
821 {
822 "domain": "usageLimits",
823 "reason": "accessNotConfigured",
824 "message": "Access Not Configured"
825 }
826 ],
827 "code": 403,
828 "message": "Access Not Configured"
829 }
830]"""
831
Joe Gregorio654f4a22012-02-09 14:15:44 -0500832class Callbacks(object):
833 def __init__(self):
834 self.responses = {}
835 self.exceptions = {}
836
837 def f(self, request_id, response, exception):
838 self.responses[request_id] = response
839 self.exceptions[request_id] = exception
840
841
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500842class TestHttpRequest(unittest.TestCase):
843 def test_unicode(self):
844 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
845 model = JsonModel()
846 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
847 method = u'POST'
848 request = HttpRequest(
849 http,
850 model.response,
851 uri,
852 method=method,
853 body=u'{}',
854 headers={'content-type': 'application/json'})
855 request.execute()
856 self.assertEqual(uri, http.uri)
857 self.assertEqual(str, type(http.uri))
858 self.assertEqual(method, http.method)
859 self.assertEqual(str, type(http.method))
860
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100861 def test_empty_content_type(self):
862 """Test for #284"""
863 http = HttpMock(None, headers={'status': 200})
864 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
865 method = u'POST'
866 request = HttpRequest(
867 http,
868 _postproc_none,
869 uri,
870 method=method,
871 headers={'content-type': ''})
872 request.execute()
873 self.assertEqual('', http.headers.get('content-type'))
Xiaofei Wang20b67582019-07-17 11:16:53 -0700874
eesheeshc6425a02016-02-12 15:07:06 +0000875 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100876 model = JsonModel()
877 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000878 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
879 model.response,
880 u'https://www.example.com/json_api_endpoint')
881 request._sleep = lambda _x: 0 # do nothing
882 request._rand = lambda: 10
883 with self.assertRaises(socket.error):
884 response = request.execute(num_retries=3)
885
886
887 def test_retry_connection_errors_non_resumable(self):
888 model = JsonModel()
889 request = HttpRequest(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400890 HttpMockWithErrors(4, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100891 model.response,
892 u'https://www.example.com/json_api_endpoint')
893 request._sleep = lambda _x: 0 # do nothing
894 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400895 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100896 self.assertEqual({u'foo': u'bar'}, response)
897
eesheeshc6425a02016-02-12 15:07:06 +0000898 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100899 with open(datafile('small.png'), 'rb') as small_png_file:
900 small_png_fd = BytesIO(small_png_file.read())
901 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
902 chunksize=500, resumable=True)
903 model = JsonModel()
904
905 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000906 HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400907 4, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100908 model.response,
909 u'https://www.example.com/file_upload',
910 method='POST',
911 resumable=upload)
912 request._sleep = lambda _x: 0 # do nothing
913 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400914 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100915 self.assertEqual({u'foo': u'bar'}, response)
916
Joe Gregorio9086bd32013-06-14 16:32:05 -0400917 def test_retry(self):
918 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000919 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
920 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
921 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
922 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400923 resp_seq.append(({'status': '200'}, '{}'))
924
925 http = HttpMockSequence(resp_seq)
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 sleeptimes = []
938 request._sleep = lambda x: sleeptimes.append(x)
939 request._rand = lambda: 10
940
941 request.execute(num_retries=num_retries)
942
943 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900944 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400945 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
946
eesheeshc6425a02016-02-12 15:07:06 +0000947 def test_no_retry_succeeds(self):
948 num_retries = 5
949 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
950
951 http = HttpMockSequence(resp_seq)
952 model = JsonModel()
953 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
954 method = u'POST'
955 request = HttpRequest(
956 http,
957 model.response,
958 uri,
959 method=method,
960 body=u'{}',
961 headers={'content-type': 'application/json'})
962
963 sleeptimes = []
964 request._sleep = lambda x: sleeptimes.append(x)
965 request._rand = lambda: 10
966
967 request.execute(num_retries=num_retries)
968
969 self.assertEqual(0, len(sleeptimes))
970
Joe Gregorio9086bd32013-06-14 16:32:05 -0400971 def test_no_retry_fails_fast(self):
972 http = HttpMockSequence([
973 ({'status': '500'}, ''),
974 ({'status': '200'}, '{}')
975 ])
976 model = JsonModel()
977 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
978 method = u'POST'
979 request = HttpRequest(
980 http,
981 model.response,
982 uri,
983 method=method,
984 body=u'{}',
985 headers={'content-type': 'application/json'})
986
987 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000988 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400989
eesheeshc6425a02016-02-12 15:07:06 +0000990 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400991 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000992 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400993
eesheeshc6425a02016-02-12 15:07:06 +0000994 def test_no_retry_403_not_configured_fails_fast(self):
995 http = HttpMockSequence([
996 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
997 ({'status': '200'}, '{}')
998 ])
999 model = JsonModel()
1000 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1001 method = u'POST'
1002 request = HttpRequest(
1003 http,
1004 model.response,
1005 uri,
1006 method=method,
1007 body=u'{}',
1008 headers={'content-type': 'application/json'})
1009
1010 request._rand = lambda: 1.0
1011 request._sleep = mock.MagicMock()
1012
1013 with self.assertRaises(HttpError):
1014 request.execute()
1015 request._sleep.assert_not_called()
1016
1017 def test_no_retry_403_fails_fast(self):
1018 http = HttpMockSequence([
1019 ({'status': '403'}, ''),
1020 ({'status': '200'}, '{}')
1021 ])
1022 model = JsonModel()
1023 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1024 method = u'POST'
1025 request = HttpRequest(
1026 http,
1027 model.response,
1028 uri,
1029 method=method,
1030 body=u'{}',
1031 headers={'content-type': 'application/json'})
1032
1033 request._rand = lambda: 1.0
1034 request._sleep = mock.MagicMock()
1035
1036 with self.assertRaises(HttpError):
1037 request.execute()
1038 request._sleep.assert_not_called()
1039
1040 def test_no_retry_401_fails_fast(self):
1041 http = HttpMockSequence([
1042 ({'status': '401'}, ''),
1043 ({'status': '200'}, '{}')
1044 ])
1045 model = JsonModel()
1046 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1047 method = u'POST'
1048 request = HttpRequest(
1049 http,
1050 model.response,
1051 uri,
1052 method=method,
1053 body=u'{}',
1054 headers={'content-type': 'application/json'})
1055
1056 request._rand = lambda: 1.0
1057 request._sleep = mock.MagicMock()
1058
1059 with self.assertRaises(HttpError):
1060 request.execute()
1061 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001062
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001063 def test_no_retry_403_list_fails(self):
1064 http = HttpMockSequence([
1065 ({'status': '403'}, LIST_NOT_CONFIGURED_RESPONSE),
1066 ({'status': '200'}, '{}')
1067 ])
1068 model = JsonModel()
1069 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1070 method = u'POST'
1071 request = HttpRequest(
1072 http,
1073 model.response,
1074 uri,
1075 method=method,
1076 body=u'{}',
1077 headers={'content-type': 'application/json'})
1078
1079 request._rand = lambda: 1.0
1080 request._sleep = mock.MagicMock()
1081
1082 with self.assertRaises(HttpError):
1083 request.execute()
1084 request._sleep.assert_not_called()
1085
Joe Gregorio66f57522011-11-30 11:00:00 -05001086class TestBatch(unittest.TestCase):
1087
1088 def setUp(self):
1089 model = JsonModel()
1090 self.request1 = HttpRequest(
1091 None,
1092 model.response,
1093 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1094 method='POST',
1095 body='{}',
1096 headers={'content-type': 'application/json'})
1097
1098 self.request2 = HttpRequest(
1099 None,
1100 model.response,
1101 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001102 method='GET',
1103 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -05001104 headers={'content-type': 'application/json'})
1105
1106
1107 def test_id_to_from_content_id_header(self):
1108 batch = BatchHttpRequest()
1109 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
1110
1111 def test_invalid_content_id_header(self):
1112 batch = BatchHttpRequest()
1113 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
1114 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
1115 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
1116
1117 def test_serialize_request(self):
1118 batch = BatchHttpRequest()
1119 request = HttpRequest(
1120 None,
1121 None,
1122 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1123 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001124 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -05001125 headers={'content-type': 'application/json'},
1126 methodId=None,
1127 resumable=None)
1128 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001129 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001130
Joe Gregoriodd813822012-01-25 10:32:47 -05001131 def test_serialize_request_media_body(self):
1132 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001133 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001134 body = f.read()
1135 f.close()
1136
1137 request = HttpRequest(
1138 None,
1139 None,
1140 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1141 method='POST',
1142 body=body,
1143 headers={'content-type': 'application/json'},
1144 methodId=None,
1145 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001146 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001147 s = batch._serialize_request(request).splitlines()
1148
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001149 def test_serialize_request_no_body(self):
1150 batch = BatchHttpRequest()
1151 request = HttpRequest(
1152 None,
1153 None,
1154 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1155 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001156 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001157 headers={'content-type': 'application/json'},
1158 methodId=None,
1159 resumable=None)
1160 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001161 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001162
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001163 def test_serialize_get_request_no_body(self):
1164 batch = BatchHttpRequest()
1165 request = HttpRequest(
1166 None,
1167 None,
1168 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1169 method='GET',
1170 body=None,
1171 headers={'content-type': 'application/json'},
1172 methodId=None,
1173 resumable=None)
1174 s = batch._serialize_request(request).splitlines()
1175 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1176
Joe Gregorio66f57522011-11-30 11:00:00 -05001177 def test_deserialize_response(self):
1178 batch = BatchHttpRequest()
1179 resp, content = batch._deserialize_response(RESPONSE)
1180
Joe Gregorio654f4a22012-02-09 14:15:44 -05001181 self.assertEqual(200, resp.status)
1182 self.assertEqual('OK', resp.reason)
1183 self.assertEqual(11, resp.version)
1184 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001185
1186 def test_new_id(self):
1187 batch = BatchHttpRequest()
1188
1189 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001190 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001191
1192 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001193 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001194
1195 batch.add(self.request1, request_id='3')
1196
1197 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001198 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001199
1200 def test_add(self):
1201 batch = BatchHttpRequest()
1202 batch.add(self.request1, request_id='1')
1203 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1204
Xinan Line2dccec2018-12-07 05:28:33 +09001205 def test_add_fail_for_over_limit(self):
1206 from googleapiclient.http import MAX_BATCH_LIMIT
1207
1208 batch = BatchHttpRequest()
Bu Sun Kimeb4b3e02018-12-12 10:41:03 -08001209 for i in range(0, MAX_BATCH_LIMIT):
Xinan Line2dccec2018-12-07 05:28:33 +09001210 batch.add(HttpRequest(
1211 None,
1212 None,
1213 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1214 method='POST',
1215 body='{}',
1216 headers={'content-type': 'application/json'})
1217 )
1218 self.assertRaises(BatchError, batch.add, self.request1)
1219
Joe Gregorio66f57522011-11-30 11:00:00 -05001220 def test_add_fail_for_resumable(self):
1221 batch = BatchHttpRequest()
1222
1223 upload = MediaFileUpload(
1224 datafile('small.png'), chunksize=500, resumable=True)
1225 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001226 with self.assertRaises(BatchError) as batch_error:
1227 batch.add(self.request1, request_id='1')
1228 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001229
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001230 def test_execute_empty_batch_no_http(self):
1231 batch = BatchHttpRequest()
1232 ret = batch.execute()
1233 self.assertEqual(None, ret)
1234
Joe Gregorio66f57522011-11-30 11:00:00 -05001235 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001236 batch = BatchHttpRequest()
1237 callbacks = Callbacks()
1238
1239 batch.add(self.request1, callback=callbacks.f)
1240 batch.add(self.request2, callback=callbacks.f)
1241 http = HttpMockSequence([
1242 ({'status': '200',
1243 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1244 BATCH_RESPONSE),
1245 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001246 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001247 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1248 self.assertEqual(None, callbacks.exceptions['1'])
1249 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1250 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001251
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001252 def test_execute_request_body(self):
1253 batch = BatchHttpRequest()
1254
1255 batch.add(self.request1)
1256 batch.add(self.request2)
1257 http = HttpMockSequence([
1258 ({'status': '200',
1259 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1260 'echo_request_body'),
1261 ])
1262 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001263 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001264 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001265 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001266 boundary, _ = e.content.split(None, 1)
1267 self.assertEqual('--', boundary[:2])
1268 parts = e.content.split(boundary)
1269 self.assertEqual(4, len(parts))
1270 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001271 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001272 header = parts[1].splitlines()[1]
1273 self.assertEqual('Content-Type: application/http', header)
1274
Chris McDonough3cf5e602018-07-18 16:18:38 -04001275 def test_execute_request_body_with_custom_long_request_ids(self):
1276 batch = BatchHttpRequest()
1277
1278 batch.add(self.request1, request_id='abc'*20)
1279 batch.add(self.request2, request_id='def'*20)
1280 http = HttpMockSequence([
1281 ({'status': '200',
1282 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1283 'echo_request_body'),
1284 ])
1285 try:
1286 batch.execute(http=http)
1287 self.fail('Should raise exception')
1288 except BatchError as e:
1289 boundary, _ = e.content.split(None, 1)
1290 self.assertEqual('--', boundary[:2])
1291 parts = e.content.split(boundary)
1292 self.assertEqual(4, len(parts))
1293 self.assertEqual('', parts[0])
1294 self.assertEqual('--', parts[3].rstrip())
1295 for partindex, request_id in ((1, 'abc'*20), (2, 'def'*20)):
1296 lines = parts[partindex].splitlines()
1297 for n, line in enumerate(lines):
1298 if line.startswith('Content-ID:'):
1299 # assert correct header folding
1300 self.assertTrue(line.endswith('+'), line)
1301 header_continuation = lines[n+1]
1302 self.assertEqual(
1303 header_continuation,
1304 ' %s>' % request_id,
1305 header_continuation
1306 )
1307
Gabriel Garcia23174be2016-05-25 17:28:07 +02001308 def test_execute_initial_refresh_oauth2(self):
1309 batch = BatchHttpRequest()
1310 callbacks = Callbacks()
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -07001311 cred = MockCredentials('Foo', expired=True)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001312
1313 http = HttpMockSequence([
1314 ({'status': '200',
1315 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1316 BATCH_SINGLE_RESPONSE),
1317 ])
1318
1319 cred.authorize(http)
1320
1321 batch.add(self.request1, callback=callbacks.f)
1322 batch.execute(http=http)
1323
1324 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1325 self.assertIsNone(callbacks.exceptions['1'])
1326
1327 self.assertEqual(1, cred._refreshed)
1328
1329 self.assertEqual(1, cred._authorized)
1330
1331 self.assertEqual(1, cred._applied)
1332
Joe Gregorio654f4a22012-02-09 14:15:44 -05001333 def test_execute_refresh_and_retry_on_401(self):
1334 batch = BatchHttpRequest()
1335 callbacks = Callbacks()
1336 cred_1 = MockCredentials('Foo')
1337 cred_2 = MockCredentials('Bar')
1338
1339 http = HttpMockSequence([
1340 ({'status': '200',
1341 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1342 BATCH_RESPONSE_WITH_401),
1343 ({'status': '200',
1344 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1345 BATCH_SINGLE_RESPONSE),
1346 ])
1347
1348 creds_http_1 = HttpMockSequence([])
1349 cred_1.authorize(creds_http_1)
1350
1351 creds_http_2 = HttpMockSequence([])
1352 cred_2.authorize(creds_http_2)
1353
1354 self.request1.http = creds_http_1
1355 self.request2.http = creds_http_2
1356
1357 batch.add(self.request1, callback=callbacks.f)
1358 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001359 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001360
1361 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1362 self.assertEqual(None, callbacks.exceptions['1'])
1363 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1364 self.assertEqual(None, callbacks.exceptions['2'])
1365
1366 self.assertEqual(1, cred_1._refreshed)
1367 self.assertEqual(0, cred_2._refreshed)
1368
1369 self.assertEqual(1, cred_1._authorized)
1370 self.assertEqual(1, cred_2._authorized)
1371
1372 self.assertEqual(1, cred_2._applied)
1373 self.assertEqual(2, cred_1._applied)
1374
1375 def test_http_errors_passed_to_callback(self):
1376 batch = BatchHttpRequest()
1377 callbacks = Callbacks()
1378 cred_1 = MockCredentials('Foo')
1379 cred_2 = MockCredentials('Bar')
1380
1381 http = HttpMockSequence([
1382 ({'status': '200',
1383 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1384 BATCH_RESPONSE_WITH_401),
1385 ({'status': '200',
1386 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1387 BATCH_RESPONSE_WITH_401),
1388 ])
1389
1390 creds_http_1 = HttpMockSequence([])
1391 cred_1.authorize(creds_http_1)
1392
1393 creds_http_2 = HttpMockSequence([])
1394 cred_2.authorize(creds_http_2)
1395
1396 self.request1.http = creds_http_1
1397 self.request2.http = creds_http_2
1398
1399 batch.add(self.request1, callback=callbacks.f)
1400 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001401 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001402
1403 self.assertEqual(None, callbacks.responses['1'])
1404 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001405 self.assertEqual(
1406 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001407 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1408 self.assertEqual(None, callbacks.exceptions['2'])
1409
Joe Gregorio66f57522011-11-30 11:00:00 -05001410 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001411 callbacks = Callbacks()
1412 batch = BatchHttpRequest(callback=callbacks.f)
1413
1414 batch.add(self.request1)
1415 batch.add(self.request2)
1416 http = HttpMockSequence([
1417 ({'status': '200',
1418 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1419 BATCH_RESPONSE),
1420 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001421 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001422 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1423 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001424
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001425 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001426 callbacks = Callbacks()
1427 batch = BatchHttpRequest(callback=callbacks.f)
1428
1429 batch.add(self.request1)
1430 batch.add(self.request2)
1431 http = HttpMockSequence([
1432 ({'status': '200',
1433 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1434 BATCH_ERROR_RESPONSE),
1435 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001436 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001437 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1438 expected = ('<HttpError 403 when requesting '
1439 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1440 '"Access Not Configured">')
1441 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001442
Joe Gregorio5c120db2012-08-23 09:13:55 -04001443
Joe Gregorioba5c7902012-08-03 12:48:16 -04001444class TestRequestUriTooLong(unittest.TestCase):
1445
1446 def test_turn_get_into_post(self):
1447
1448 def _postproc(resp, content):
1449 return content
1450
1451 http = HttpMockSequence([
1452 ({'status': '200'},
1453 'echo_request_body'),
1454 ({'status': '200'},
1455 'echo_request_headers'),
1456 ])
1457
1458 # Send a long query parameter.
1459 query = {
1460 'q': 'a' * MAX_URI_LENGTH + '?&'
1461 }
1462 req = HttpRequest(
1463 http,
1464 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001465 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001466 method='GET',
1467 body=None,
1468 headers={},
1469 methodId='foo',
1470 resumable=None)
1471
1472 # Query parameters should be sent in the body.
1473 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001474 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001475
1476 # Extra headers should be set.
1477 response = req.execute()
1478 self.assertEqual('GET', response['x-http-method-override'])
1479 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1480 self.assertEqual(
1481 'application/x-www-form-urlencoded', response['content-type'])
1482
Joe Gregorio5c120db2012-08-23 09:13:55 -04001483
1484class TestStreamSlice(unittest.TestCase):
1485 """Test _StreamSlice."""
1486
1487 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001488 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001489
1490 def test_read(self):
1491 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001492 self.assertEqual(b'', s.read(0))
1493 self.assertEqual(b'0', s.read(1))
1494 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001495
1496 def test_read_too_much(self):
1497 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001498 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001499
1500 def test_read_all(self):
1501 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001502 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001503
Ali Afshar164f37e2013-01-07 14:05:45 -08001504
1505class TestResponseCallback(unittest.TestCase):
1506 """Test adding callbacks to responses."""
1507
1508 def test_ensure_response_callback(self):
1509 m = JsonModel()
1510 request = HttpRequest(
1511 None,
1512 m.response,
1513 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1514 method='POST',
1515 body='{}',
1516 headers={'content-type': 'application/json'})
1517 h = HttpMockSequence([ ({'status': 200}, '{}')])
1518 responses = []
1519 def _on_response(resp, responses=responses):
1520 responses.append(resp)
1521 request.add_response_callback(_on_response)
1522 request.execute(http=h)
1523 self.assertEqual(1, len(responses))
1524
1525
Craig Gurnik8e55b762015-01-20 15:00:10 -05001526class TestHttpMock(unittest.TestCase):
1527 def test_default_response_headers(self):
1528 http = HttpMock(datafile('zoo.json'))
1529 resp, content = http.request("http://example.com")
1530 self.assertEqual(resp.status, 200)
1531
Alan Briolat26b01002015-08-14 00:13:57 +01001532 def test_error_response(self):
1533 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1534 model = JsonModel()
1535 request = HttpRequest(
1536 http,
1537 model.response,
1538 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1539 method='GET',
1540 headers={})
1541 self.assertRaises(HttpError, request.execute)
1542
Craig Gurnik8e55b762015-01-20 15:00:10 -05001543
Igor Maravić22435292017-01-19 22:28:22 +01001544class TestHttpBuild(unittest.TestCase):
1545 original_socket_default_timeout = None
1546
1547 @classmethod
1548 def setUpClass(cls):
1549 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1550
1551 @classmethod
1552 def tearDownClass(cls):
1553 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1554
1555 def test_build_http_sets_default_timeout_if_none_specified(self):
1556 socket.setdefaulttimeout(None)
1557 http = build_http()
1558 self.assertIsInstance(http.timeout, int)
1559 self.assertGreater(http.timeout, 0)
1560
1561 def test_build_http_default_timeout_can_be_overridden(self):
1562 socket.setdefaulttimeout(1.5)
1563 http = build_http()
1564 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1565
1566 def test_build_http_default_timeout_can_be_set_to_zero(self):
1567 socket.setdefaulttimeout(0)
1568 http = build_http()
1569 self.assertEquals(http.timeout, 0)
1570
1571
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001572if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001573 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001574 unittest.main()