blob: 5aaada68a908658f654457a2a5dce02b37eb2d9f [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
488 self.assertEqual(download._headers, {'Cache-Control':'no-store'})
489
490 status, done = download.next_chunk()
491
492 result = self.fd.getvalue().decode('utf-8')
493
494 # we abuse the internals of the object we're testing, pay no attention
495 # to the actual bytes= values here; we are just asserting that the
496 # header we added to the original request is sent up to the server
497 # on each call to next_chunk
498
499 self.assertEqual(json.loads(result),
500 {"Cache-Control": "no-store", "range": "bytes=0-3"})
501
502 download._fd = self.fd = BytesIO()
503 status, done = download.next_chunk()
504
505 result = self.fd.getvalue().decode('utf-8')
506 self.assertEqual(json.loads(result),
507 {"Cache-Control": "no-store", "range": "bytes=51-54"})
508
Joe Gregorio708388c2012-06-15 13:43:04 -0400509 def test_media_io_base_download_handle_redirects(self):
510 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400511 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800512 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400513 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800514 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400515 ])
516
517 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400518 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400519
520 status, done = download.next_chunk()
521
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400522 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400523
524 def test_media_io_base_download_handle_4xx(self):
525 self.request.http = HttpMockSequence([
526 ({'status': '400'}, ''),
527 ])
528
529 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400530 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400531
532 try:
533 status, done = download.next_chunk()
534 self.fail('Should raise an exception')
535 except HttpError:
536 pass
537
538 # Even after raising an exception we can pick up where we left off.
539 self.request.http = HttpMockSequence([
540 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800541 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400542 ])
543
544 status, done = download.next_chunk()
545
Pat Ferate2b140222015-03-03 18:05:11 -0800546 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400547
eesheeshc6425a02016-02-12 15:07:06 +0000548 def test_media_io_base_download_retries_connection_errors(self):
549 self.request.http = HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400550 4, {'status': '200', 'content-range': '0-2/3'}, b'123')
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100551
552 download = MediaIoBaseDownload(
553 fd=self.fd, request=self.request, chunksize=3)
554 download._sleep = lambda _x: 0 # do nothing
555 download._rand = lambda: 10
556
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400557 status, done = download.next_chunk(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100558
559 self.assertEqual(self.fd.getvalue(), b'123')
560 self.assertEqual(True, done)
561
Joe Gregorio9086bd32013-06-14 16:32:05 -0400562 def test_media_io_base_download_retries_5xx(self):
563 self.request.http = HttpMockSequence([
564 ({'status': '500'}, ''),
565 ({'status': '500'}, ''),
566 ({'status': '500'}, ''),
567 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800568 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400569 ({'status': '503'}, ''),
570 ({'status': '503'}, ''),
571 ({'status': '503'}, ''),
572 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800573 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400574 ])
575
576 download = MediaIoBaseDownload(
577 fd=self.fd, request=self.request, chunksize=3)
578
579 self.assertEqual(self.fd, download._fd)
580 self.assertEqual(3, download._chunksize)
581 self.assertEqual(0, download._progress)
582 self.assertEqual(None, download._total_size)
583 self.assertEqual(False, download._done)
584 self.assertEqual(self.request.uri, download._uri)
585
586 # Set time.sleep and random.random stubs.
587 sleeptimes = []
588 download._sleep = lambda x: sleeptimes.append(x)
589 download._rand = lambda: 10
590
591 status, done = download.next_chunk(num_retries=3)
592
593 # Check for exponential backoff using the rand function above.
594 self.assertEqual([20, 40, 80], sleeptimes)
595
Pat Ferate2b140222015-03-03 18:05:11 -0800596 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400597 self.assertEqual(False, done)
598 self.assertEqual(3, download._progress)
599 self.assertEqual(5, download._total_size)
600 self.assertEqual(3, status.resumable_progress)
601
602 # Reset time.sleep stub.
603 del sleeptimes[0:len(sleeptimes)]
604
605 status, done = download.next_chunk(num_retries=3)
606
607 # Check for exponential backoff using the rand function above.
608 self.assertEqual([20, 40, 80], sleeptimes)
609
Pat Ferate2b140222015-03-03 18:05:11 -0800610 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400611 self.assertEqual(True, done)
612 self.assertEqual(5, download._progress)
613 self.assertEqual(5, download._total_size)
614
andrewnestera4a44cf2017-03-31 16:09:31 +0300615 def test_media_io_base_download_empty_file(self):
616 self.request.http = HttpMockSequence([
617 ({'status': '200',
618 'content-range': '0-0/0'}, b''),
619 ])
620
621 download = MediaIoBaseDownload(
622 fd=self.fd, request=self.request, chunksize=3)
623
624 self.assertEqual(self.fd, download._fd)
625 self.assertEqual(0, download._progress)
626 self.assertEqual(None, download._total_size)
627 self.assertEqual(False, download._done)
628 self.assertEqual(self.request.uri, download._uri)
629
630 status, done = download.next_chunk()
631
632 self.assertEqual(True, done)
633 self.assertEqual(0, download._progress)
634 self.assertEqual(0, download._total_size)
635 self.assertEqual(0, status.progress())
636
Daniel44067782018-01-16 23:17:56 +0100637 def test_media_io_base_download_unknown_media_size(self):
638 self.request.http = HttpMockSequence([
639 ({'status': '200'}, b'123')
640 ])
641
642 download = MediaIoBaseDownload(
643 fd=self.fd, request=self.request, chunksize=3)
644
645 self.assertEqual(self.fd, download._fd)
646 self.assertEqual(0, download._progress)
647 self.assertEqual(None, download._total_size)
648 self.assertEqual(False, download._done)
649 self.assertEqual(self.request.uri, download._uri)
650
651 status, done = download.next_chunk()
652
653 self.assertEqual(self.fd.getvalue(), b'123')
654 self.assertEqual(True, done)
655 self.assertEqual(3, download._progress)
656 self.assertEqual(None, download._total_size)
657 self.assertEqual(0, status.progress())
658
659
Joe Gregorio66f57522011-11-30 11:00:00 -0500660EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
661Content-Type: application/json
662MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500663Host: www.googleapis.com
664content-length: 2\r\n\r\n{}"""
665
666
667NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
668Content-Type: application/json
669MIME-Version: 1.0
670Host: www.googleapis.com
671content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500672
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400673NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
674Content-Type: application/json
675MIME-Version: 1.0
676Host: www.googleapis.com\r\n\r\n"""
677
Joe Gregorio66f57522011-11-30 11:00:00 -0500678
679RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400680Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500681Content-Length: 14
682ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
683
684
INADA Naoki09157612015-03-25 01:51:03 +0900685BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500686Content-Type: application/http
687Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400688Content-ID: <randomness + 1>
Joe Gregorio66f57522011-11-30 11:00:00 -0500689
690HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400691Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500692Content-Length: 14
693ETag: "etag/pony"\r\n\r\n{"foo": 42}
694
695--batch_foobarbaz
696Content-Type: application/http
697Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400698Content-ID: <randomness + 2>
Joe Gregorio66f57522011-11-30 11:00:00 -0500699
700HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400701Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500702Content-Length: 14
703ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
704--batch_foobarbaz--"""
705
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500706
INADA Naoki09157612015-03-25 01:51:03 +0900707BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400708Content-Type: application/http
709Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400710Content-ID: <randomness + 1>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400711
712HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400713Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400714Content-Length: 14
715ETag: "etag/pony"\r\n\r\n{"foo": 42}
716
717--batch_foobarbaz
718Content-Type: application/http
719Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400720Content-ID: <randomness + 2>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400721
722HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400723Content-Type: application/json
724Content-Length: 245
725ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400726 "error": {
727 "errors": [
728 {
729 "domain": "usageLimits",
730 "reason": "accessNotConfigured",
731 "message": "Access Not Configured",
732 "debugInfo": "QuotaState: BLOCKED"
733 }
734 ],
735 "code": 403,
736 "message": "Access Not Configured"
737 }
738}
739
740--batch_foobarbaz--"""
741
742
INADA Naoki09157612015-03-25 01:51:03 +0900743BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500744Content-Type: application/http
745Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400746Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500747
Joe Gregorioc752e332012-07-11 14:43:52 -0400748HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400749Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500750Content-Length: 14
751ETag: "etag/pony"\r\n\r\n{"error": {"message":
752 "Authorizaton failed."}}
753
754--batch_foobarbaz
755Content-Type: application/http
756Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400757Content-ID: <randomness + 2>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500758
759HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400760Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500761Content-Length: 14
762ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
763--batch_foobarbaz--"""
764
765
INADA Naoki09157612015-03-25 01:51:03 +0900766BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500767Content-Type: application/http
768Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400769Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500770
771HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400772Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500773Content-Length: 14
774ETag: "etag/pony"\r\n\r\n{"foo": 42}
775--batch_foobarbaz--"""
776
eesheeshc6425a02016-02-12 15:07:06 +0000777
778USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
779 "error": {
780 "errors": [
781 {
782 "domain": "usageLimits",
783 "reason": "userRateLimitExceeded",
784 "message": "User Rate Limit Exceeded"
785 }
786 ],
787 "code": 403,
788 "message": "User Rate Limit Exceeded"
789 }
790}"""
791
792
793RATE_LIMIT_EXCEEDED_RESPONSE = """{
794 "error": {
795 "errors": [
796 {
797 "domain": "usageLimits",
798 "reason": "rateLimitExceeded",
799 "message": "Rate Limit Exceeded"
800 }
801 ],
802 "code": 403,
803 "message": "Rate Limit Exceeded"
804 }
805}"""
806
807
808NOT_CONFIGURED_RESPONSE = """{
809 "error": {
810 "errors": [
811 {
812 "domain": "usageLimits",
813 "reason": "accessNotConfigured",
814 "message": "Access Not Configured"
815 }
816 ],
817 "code": 403,
818 "message": "Access Not Configured"
819 }
820}"""
821
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800822LIST_NOT_CONFIGURED_RESPONSE = """[
823 "error": {
824 "errors": [
825 {
826 "domain": "usageLimits",
827 "reason": "accessNotConfigured",
828 "message": "Access Not Configured"
829 }
830 ],
831 "code": 403,
832 "message": "Access Not Configured"
833 }
834]"""
835
Joe Gregorio654f4a22012-02-09 14:15:44 -0500836class Callbacks(object):
837 def __init__(self):
838 self.responses = {}
839 self.exceptions = {}
840
841 def f(self, request_id, response, exception):
842 self.responses[request_id] = response
843 self.exceptions[request_id] = exception
844
845
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500846class TestHttpRequest(unittest.TestCase):
847 def test_unicode(self):
848 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
849 model = JsonModel()
850 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
851 method = u'POST'
852 request = HttpRequest(
853 http,
854 model.response,
855 uri,
856 method=method,
857 body=u'{}',
858 headers={'content-type': 'application/json'})
859 request.execute()
860 self.assertEqual(uri, http.uri)
861 self.assertEqual(str, type(http.uri))
862 self.assertEqual(method, http.method)
863 self.assertEqual(str, type(http.method))
864
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100865 def test_empty_content_type(self):
866 """Test for #284"""
867 http = HttpMock(None, headers={'status': 200})
868 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
869 method = u'POST'
870 request = HttpRequest(
871 http,
872 _postproc_none,
873 uri,
874 method=method,
875 headers={'content-type': ''})
876 request.execute()
877 self.assertEqual('', http.headers.get('content-type'))
Xiaofei Wang20b67582019-07-17 11:16:53 -0700878
eesheeshc6425a02016-02-12 15:07:06 +0000879 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100880 model = JsonModel()
881 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000882 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
883 model.response,
884 u'https://www.example.com/json_api_endpoint')
885 request._sleep = lambda _x: 0 # do nothing
886 request._rand = lambda: 10
887 with self.assertRaises(socket.error):
888 response = request.execute(num_retries=3)
889
890
891 def test_retry_connection_errors_non_resumable(self):
892 model = JsonModel()
893 request = HttpRequest(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400894 HttpMockWithErrors(4, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100895 model.response,
896 u'https://www.example.com/json_api_endpoint')
897 request._sleep = lambda _x: 0 # do nothing
898 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400899 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100900 self.assertEqual({u'foo': u'bar'}, response)
901
eesheeshc6425a02016-02-12 15:07:06 +0000902 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100903 with open(datafile('small.png'), 'rb') as small_png_file:
904 small_png_fd = BytesIO(small_png_file.read())
905 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
906 chunksize=500, resumable=True)
907 model = JsonModel()
908
909 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000910 HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400911 4, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100912 model.response,
913 u'https://www.example.com/file_upload',
914 method='POST',
915 resumable=upload)
916 request._sleep = lambda _x: 0 # do nothing
917 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400918 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100919 self.assertEqual({u'foo': u'bar'}, response)
920
Joe Gregorio9086bd32013-06-14 16:32:05 -0400921 def test_retry(self):
922 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000923 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
924 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
925 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
926 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400927 resp_seq.append(({'status': '200'}, '{}'))
928
929 http = HttpMockSequence(resp_seq)
930 model = JsonModel()
931 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
932 method = u'POST'
933 request = HttpRequest(
934 http,
935 model.response,
936 uri,
937 method=method,
938 body=u'{}',
939 headers={'content-type': 'application/json'})
940
941 sleeptimes = []
942 request._sleep = lambda x: sleeptimes.append(x)
943 request._rand = lambda: 10
944
945 request.execute(num_retries=num_retries)
946
947 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900948 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400949 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
950
eesheeshc6425a02016-02-12 15:07:06 +0000951 def test_no_retry_succeeds(self):
952 num_retries = 5
953 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
954
955 http = HttpMockSequence(resp_seq)
956 model = JsonModel()
957 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
958 method = u'POST'
959 request = HttpRequest(
960 http,
961 model.response,
962 uri,
963 method=method,
964 body=u'{}',
965 headers={'content-type': 'application/json'})
966
967 sleeptimes = []
968 request._sleep = lambda x: sleeptimes.append(x)
969 request._rand = lambda: 10
970
971 request.execute(num_retries=num_retries)
972
973 self.assertEqual(0, len(sleeptimes))
974
Joe Gregorio9086bd32013-06-14 16:32:05 -0400975 def test_no_retry_fails_fast(self):
976 http = HttpMockSequence([
977 ({'status': '500'}, ''),
978 ({'status': '200'}, '{}')
979 ])
980 model = JsonModel()
981 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
982 method = u'POST'
983 request = HttpRequest(
984 http,
985 model.response,
986 uri,
987 method=method,
988 body=u'{}',
989 headers={'content-type': 'application/json'})
990
991 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000992 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400993
eesheeshc6425a02016-02-12 15:07:06 +0000994 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400995 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000996 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400997
eesheeshc6425a02016-02-12 15:07:06 +0000998 def test_no_retry_403_not_configured_fails_fast(self):
999 http = HttpMockSequence([
1000 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
1001 ({'status': '200'}, '{}')
1002 ])
1003 model = JsonModel()
1004 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1005 method = u'POST'
1006 request = HttpRequest(
1007 http,
1008 model.response,
1009 uri,
1010 method=method,
1011 body=u'{}',
1012 headers={'content-type': 'application/json'})
1013
1014 request._rand = lambda: 1.0
1015 request._sleep = mock.MagicMock()
1016
1017 with self.assertRaises(HttpError):
1018 request.execute()
1019 request._sleep.assert_not_called()
1020
1021 def test_no_retry_403_fails_fast(self):
1022 http = HttpMockSequence([
1023 ({'status': '403'}, ''),
1024 ({'status': '200'}, '{}')
1025 ])
1026 model = JsonModel()
1027 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1028 method = u'POST'
1029 request = HttpRequest(
1030 http,
1031 model.response,
1032 uri,
1033 method=method,
1034 body=u'{}',
1035 headers={'content-type': 'application/json'})
1036
1037 request._rand = lambda: 1.0
1038 request._sleep = mock.MagicMock()
1039
1040 with self.assertRaises(HttpError):
1041 request.execute()
1042 request._sleep.assert_not_called()
1043
1044 def test_no_retry_401_fails_fast(self):
1045 http = HttpMockSequence([
1046 ({'status': '401'}, ''),
1047 ({'status': '200'}, '{}')
1048 ])
1049 model = JsonModel()
1050 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1051 method = u'POST'
1052 request = HttpRequest(
1053 http,
1054 model.response,
1055 uri,
1056 method=method,
1057 body=u'{}',
1058 headers={'content-type': 'application/json'})
1059
1060 request._rand = lambda: 1.0
1061 request._sleep = mock.MagicMock()
1062
1063 with self.assertRaises(HttpError):
1064 request.execute()
1065 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001066
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001067 def test_no_retry_403_list_fails(self):
1068 http = HttpMockSequence([
1069 ({'status': '403'}, LIST_NOT_CONFIGURED_RESPONSE),
1070 ({'status': '200'}, '{}')
1071 ])
1072 model = JsonModel()
1073 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1074 method = u'POST'
1075 request = HttpRequest(
1076 http,
1077 model.response,
1078 uri,
1079 method=method,
1080 body=u'{}',
1081 headers={'content-type': 'application/json'})
1082
1083 request._rand = lambda: 1.0
1084 request._sleep = mock.MagicMock()
1085
1086 with self.assertRaises(HttpError):
1087 request.execute()
1088 request._sleep.assert_not_called()
1089
Joe Gregorio66f57522011-11-30 11:00:00 -05001090class TestBatch(unittest.TestCase):
1091
1092 def setUp(self):
1093 model = JsonModel()
1094 self.request1 = HttpRequest(
1095 None,
1096 model.response,
1097 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1098 method='POST',
1099 body='{}',
1100 headers={'content-type': 'application/json'})
1101
1102 self.request2 = HttpRequest(
1103 None,
1104 model.response,
1105 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001106 method='GET',
1107 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -05001108 headers={'content-type': 'application/json'})
1109
1110
1111 def test_id_to_from_content_id_header(self):
1112 batch = BatchHttpRequest()
1113 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
1114
1115 def test_invalid_content_id_header(self):
1116 batch = BatchHttpRequest()
1117 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
1118 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
1119 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
1120
1121 def test_serialize_request(self):
1122 batch = BatchHttpRequest()
1123 request = HttpRequest(
1124 None,
1125 None,
1126 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1127 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001128 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -05001129 headers={'content-type': 'application/json'},
1130 methodId=None,
1131 resumable=None)
1132 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001133 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001134
Joe Gregoriodd813822012-01-25 10:32:47 -05001135 def test_serialize_request_media_body(self):
1136 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001137 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001138 body = f.read()
1139 f.close()
1140
1141 request = HttpRequest(
1142 None,
1143 None,
1144 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1145 method='POST',
1146 body=body,
1147 headers={'content-type': 'application/json'},
1148 methodId=None,
1149 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001150 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001151 s = batch._serialize_request(request).splitlines()
1152
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001153 def test_serialize_request_no_body(self):
1154 batch = BatchHttpRequest()
1155 request = HttpRequest(
1156 None,
1157 None,
1158 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1159 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001160 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001161 headers={'content-type': 'application/json'},
1162 methodId=None,
1163 resumable=None)
1164 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001165 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001166
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001167 def test_serialize_get_request_no_body(self):
1168 batch = BatchHttpRequest()
1169 request = HttpRequest(
1170 None,
1171 None,
1172 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1173 method='GET',
1174 body=None,
1175 headers={'content-type': 'application/json'},
1176 methodId=None,
1177 resumable=None)
1178 s = batch._serialize_request(request).splitlines()
1179 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1180
Joe Gregorio66f57522011-11-30 11:00:00 -05001181 def test_deserialize_response(self):
1182 batch = BatchHttpRequest()
1183 resp, content = batch._deserialize_response(RESPONSE)
1184
Joe Gregorio654f4a22012-02-09 14:15:44 -05001185 self.assertEqual(200, resp.status)
1186 self.assertEqual('OK', resp.reason)
1187 self.assertEqual(11, resp.version)
1188 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001189
1190 def test_new_id(self):
1191 batch = BatchHttpRequest()
1192
1193 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001194 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001195
1196 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001197 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001198
1199 batch.add(self.request1, request_id='3')
1200
1201 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001202 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001203
1204 def test_add(self):
1205 batch = BatchHttpRequest()
1206 batch.add(self.request1, request_id='1')
1207 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1208
Xinan Line2dccec2018-12-07 05:28:33 +09001209 def test_add_fail_for_over_limit(self):
1210 from googleapiclient.http import MAX_BATCH_LIMIT
1211
1212 batch = BatchHttpRequest()
Bu Sun Kimeb4b3e02018-12-12 10:41:03 -08001213 for i in range(0, MAX_BATCH_LIMIT):
Xinan Line2dccec2018-12-07 05:28:33 +09001214 batch.add(HttpRequest(
1215 None,
1216 None,
1217 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1218 method='POST',
1219 body='{}',
1220 headers={'content-type': 'application/json'})
1221 )
1222 self.assertRaises(BatchError, batch.add, self.request1)
1223
Joe Gregorio66f57522011-11-30 11:00:00 -05001224 def test_add_fail_for_resumable(self):
1225 batch = BatchHttpRequest()
1226
1227 upload = MediaFileUpload(
1228 datafile('small.png'), chunksize=500, resumable=True)
1229 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001230 with self.assertRaises(BatchError) as batch_error:
1231 batch.add(self.request1, request_id='1')
1232 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001233
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001234 def test_execute_empty_batch_no_http(self):
1235 batch = BatchHttpRequest()
1236 ret = batch.execute()
1237 self.assertEqual(None, ret)
1238
Joe Gregorio66f57522011-11-30 11:00:00 -05001239 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001240 batch = BatchHttpRequest()
1241 callbacks = Callbacks()
1242
1243 batch.add(self.request1, callback=callbacks.f)
1244 batch.add(self.request2, callback=callbacks.f)
1245 http = HttpMockSequence([
1246 ({'status': '200',
1247 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1248 BATCH_RESPONSE),
1249 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001250 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001251 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1252 self.assertEqual(None, callbacks.exceptions['1'])
1253 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1254 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001255
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001256 def test_execute_request_body(self):
1257 batch = BatchHttpRequest()
1258
1259 batch.add(self.request1)
1260 batch.add(self.request2)
1261 http = HttpMockSequence([
1262 ({'status': '200',
1263 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1264 'echo_request_body'),
1265 ])
1266 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001267 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001268 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001269 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001270 boundary, _ = e.content.split(None, 1)
1271 self.assertEqual('--', boundary[:2])
1272 parts = e.content.split(boundary)
1273 self.assertEqual(4, len(parts))
1274 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001275 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001276 header = parts[1].splitlines()[1]
1277 self.assertEqual('Content-Type: application/http', header)
1278
Chris McDonough3cf5e602018-07-18 16:18:38 -04001279 def test_execute_request_body_with_custom_long_request_ids(self):
1280 batch = BatchHttpRequest()
1281
1282 batch.add(self.request1, request_id='abc'*20)
1283 batch.add(self.request2, request_id='def'*20)
1284 http = HttpMockSequence([
1285 ({'status': '200',
1286 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1287 'echo_request_body'),
1288 ])
1289 try:
1290 batch.execute(http=http)
1291 self.fail('Should raise exception')
1292 except BatchError as e:
1293 boundary, _ = e.content.split(None, 1)
1294 self.assertEqual('--', boundary[:2])
1295 parts = e.content.split(boundary)
1296 self.assertEqual(4, len(parts))
1297 self.assertEqual('', parts[0])
1298 self.assertEqual('--', parts[3].rstrip())
1299 for partindex, request_id in ((1, 'abc'*20), (2, 'def'*20)):
1300 lines = parts[partindex].splitlines()
1301 for n, line in enumerate(lines):
1302 if line.startswith('Content-ID:'):
1303 # assert correct header folding
1304 self.assertTrue(line.endswith('+'), line)
1305 header_continuation = lines[n+1]
1306 self.assertEqual(
1307 header_continuation,
1308 ' %s>' % request_id,
1309 header_continuation
1310 )
1311
Gabriel Garcia23174be2016-05-25 17:28:07 +02001312 def test_execute_initial_refresh_oauth2(self):
1313 batch = BatchHttpRequest()
1314 callbacks = Callbacks()
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -07001315 cred = MockCredentials('Foo', expired=True)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001316
1317 http = HttpMockSequence([
1318 ({'status': '200',
1319 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1320 BATCH_SINGLE_RESPONSE),
1321 ])
1322
1323 cred.authorize(http)
1324
1325 batch.add(self.request1, callback=callbacks.f)
1326 batch.execute(http=http)
1327
1328 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1329 self.assertIsNone(callbacks.exceptions['1'])
1330
1331 self.assertEqual(1, cred._refreshed)
1332
1333 self.assertEqual(1, cred._authorized)
1334
1335 self.assertEqual(1, cred._applied)
1336
Joe Gregorio654f4a22012-02-09 14:15:44 -05001337 def test_execute_refresh_and_retry_on_401(self):
1338 batch = BatchHttpRequest()
1339 callbacks = Callbacks()
1340 cred_1 = MockCredentials('Foo')
1341 cred_2 = MockCredentials('Bar')
1342
1343 http = HttpMockSequence([
1344 ({'status': '200',
1345 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1346 BATCH_RESPONSE_WITH_401),
1347 ({'status': '200',
1348 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1349 BATCH_SINGLE_RESPONSE),
1350 ])
1351
1352 creds_http_1 = HttpMockSequence([])
1353 cred_1.authorize(creds_http_1)
1354
1355 creds_http_2 = HttpMockSequence([])
1356 cred_2.authorize(creds_http_2)
1357
1358 self.request1.http = creds_http_1
1359 self.request2.http = creds_http_2
1360
1361 batch.add(self.request1, callback=callbacks.f)
1362 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001363 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001364
1365 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1366 self.assertEqual(None, callbacks.exceptions['1'])
1367 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1368 self.assertEqual(None, callbacks.exceptions['2'])
1369
1370 self.assertEqual(1, cred_1._refreshed)
1371 self.assertEqual(0, cred_2._refreshed)
1372
1373 self.assertEqual(1, cred_1._authorized)
1374 self.assertEqual(1, cred_2._authorized)
1375
1376 self.assertEqual(1, cred_2._applied)
1377 self.assertEqual(2, cred_1._applied)
1378
1379 def test_http_errors_passed_to_callback(self):
1380 batch = BatchHttpRequest()
1381 callbacks = Callbacks()
1382 cred_1 = MockCredentials('Foo')
1383 cred_2 = MockCredentials('Bar')
1384
1385 http = HttpMockSequence([
1386 ({'status': '200',
1387 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1388 BATCH_RESPONSE_WITH_401),
1389 ({'status': '200',
1390 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1391 BATCH_RESPONSE_WITH_401),
1392 ])
1393
1394 creds_http_1 = HttpMockSequence([])
1395 cred_1.authorize(creds_http_1)
1396
1397 creds_http_2 = HttpMockSequence([])
1398 cred_2.authorize(creds_http_2)
1399
1400 self.request1.http = creds_http_1
1401 self.request2.http = creds_http_2
1402
1403 batch.add(self.request1, callback=callbacks.f)
1404 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001405 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001406
1407 self.assertEqual(None, callbacks.responses['1'])
1408 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001409 self.assertEqual(
1410 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001411 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1412 self.assertEqual(None, callbacks.exceptions['2'])
1413
Joe Gregorio66f57522011-11-30 11:00:00 -05001414 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001415 callbacks = Callbacks()
1416 batch = BatchHttpRequest(callback=callbacks.f)
1417
1418 batch.add(self.request1)
1419 batch.add(self.request2)
1420 http = HttpMockSequence([
1421 ({'status': '200',
1422 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1423 BATCH_RESPONSE),
1424 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001425 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001426 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1427 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001428
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001429 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001430 callbacks = Callbacks()
1431 batch = BatchHttpRequest(callback=callbacks.f)
1432
1433 batch.add(self.request1)
1434 batch.add(self.request2)
1435 http = HttpMockSequence([
1436 ({'status': '200',
1437 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1438 BATCH_ERROR_RESPONSE),
1439 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001440 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001441 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1442 expected = ('<HttpError 403 when requesting '
1443 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1444 '"Access Not Configured">')
1445 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001446
Joe Gregorio5c120db2012-08-23 09:13:55 -04001447
Joe Gregorioba5c7902012-08-03 12:48:16 -04001448class TestRequestUriTooLong(unittest.TestCase):
1449
1450 def test_turn_get_into_post(self):
1451
1452 def _postproc(resp, content):
1453 return content
1454
1455 http = HttpMockSequence([
1456 ({'status': '200'},
1457 'echo_request_body'),
1458 ({'status': '200'},
1459 'echo_request_headers'),
1460 ])
1461
1462 # Send a long query parameter.
1463 query = {
1464 'q': 'a' * MAX_URI_LENGTH + '?&'
1465 }
1466 req = HttpRequest(
1467 http,
1468 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001469 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001470 method='GET',
1471 body=None,
1472 headers={},
1473 methodId='foo',
1474 resumable=None)
1475
1476 # Query parameters should be sent in the body.
1477 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001478 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001479
1480 # Extra headers should be set.
1481 response = req.execute()
1482 self.assertEqual('GET', response['x-http-method-override'])
1483 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1484 self.assertEqual(
1485 'application/x-www-form-urlencoded', response['content-type'])
1486
Joe Gregorio5c120db2012-08-23 09:13:55 -04001487
1488class TestStreamSlice(unittest.TestCase):
1489 """Test _StreamSlice."""
1490
1491 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001492 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001493
1494 def test_read(self):
1495 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001496 self.assertEqual(b'', s.read(0))
1497 self.assertEqual(b'0', s.read(1))
1498 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001499
1500 def test_read_too_much(self):
1501 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001502 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001503
1504 def test_read_all(self):
1505 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001506 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001507
Ali Afshar164f37e2013-01-07 14:05:45 -08001508
1509class TestResponseCallback(unittest.TestCase):
1510 """Test adding callbacks to responses."""
1511
1512 def test_ensure_response_callback(self):
1513 m = JsonModel()
1514 request = HttpRequest(
1515 None,
1516 m.response,
1517 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1518 method='POST',
1519 body='{}',
1520 headers={'content-type': 'application/json'})
1521 h = HttpMockSequence([ ({'status': 200}, '{}')])
1522 responses = []
1523 def _on_response(resp, responses=responses):
1524 responses.append(resp)
1525 request.add_response_callback(_on_response)
1526 request.execute(http=h)
1527 self.assertEqual(1, len(responses))
1528
1529
Craig Gurnik8e55b762015-01-20 15:00:10 -05001530class TestHttpMock(unittest.TestCase):
1531 def test_default_response_headers(self):
1532 http = HttpMock(datafile('zoo.json'))
1533 resp, content = http.request("http://example.com")
1534 self.assertEqual(resp.status, 200)
1535
Alan Briolat26b01002015-08-14 00:13:57 +01001536 def test_error_response(self):
1537 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1538 model = JsonModel()
1539 request = HttpRequest(
1540 http,
1541 model.response,
1542 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1543 method='GET',
1544 headers={})
1545 self.assertRaises(HttpError, request.execute)
1546
Craig Gurnik8e55b762015-01-20 15:00:10 -05001547
Igor Maravić22435292017-01-19 22:28:22 +01001548class TestHttpBuild(unittest.TestCase):
1549 original_socket_default_timeout = None
1550
1551 @classmethod
1552 def setUpClass(cls):
1553 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1554
1555 @classmethod
1556 def tearDownClass(cls):
1557 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1558
1559 def test_build_http_sets_default_timeout_if_none_specified(self):
1560 socket.setdefaulttimeout(None)
1561 http = build_http()
1562 self.assertIsInstance(http.timeout, int)
1563 self.assertGreater(http.timeout, 0)
1564
1565 def test_build_http_default_timeout_can_be_overridden(self):
1566 socket.setdefaulttimeout(1.5)
1567 http = build_http()
1568 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1569
1570 def test_build_http_default_timeout_can_be_set_to_zero(self):
1571 socket.setdefaulttimeout(0)
1572 http = build_http()
1573 self.assertEquals(http.timeout, 0)
1574
1575
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001576if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001577 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001578 unittest.main()