blob: 0df00ab33bafdb1f8a58384d40da8acf38687db6 [file] [log] [blame]
Craig Citro15744b12015-03-02 13:34:32 -08001#!/usr/bin/env python
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05002#
Craig Citro751b7fb2014-09-23 11:20:38 -07003# Copyright 2014 Google Inc. All Rights Reserved.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Http tests
18
John Asmuth864311d2014-04-24 15:46:08 -040019Unit tests for the googleapiclient.http.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050020"""
INADA Naokid898a372015-03-04 03:52:46 +090021from __future__ import absolute_import
22from six.moves import range
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050023
24__author__ = 'jcgregorio@google.com (Joe Gregorio)'
25
Pat Feratec6050872015-03-03 18:24:59 -080026from six import PY3
Pat Ferateed9affd2015-03-03 16:03:15 -080027from six import BytesIO, StringIO
28from io import FileIO
Pat Ferated5b61bd2015-03-03 16:04:11 -080029from six.moves.urllib.parse import urlencode
Pat Ferateed9affd2015-03-03 16:03:15 -080030
Joe Gregorio7cbceab2011-06-27 10:46:54 -040031# Do not remove the httplib2 import
32import httplib2
Joe Gregorio9086bd32013-06-14 16:32:05 -040033import logging
eesheeshc6425a02016-02-12 15:07:06 +000034import mock
Joe Gregoriod0bd3882011-11-22 09:49:47 -050035import os
Pat Ferate497a90f2015-03-09 09:52:54 -070036import unittest2 as unittest
Joe Gregorio9086bd32013-06-14 16:32:05 -040037import random
eesheeshc6425a02016-02-12 15:07:06 +000038import socket
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +010039import ssl
Joe Gregorio9086bd32013-06-14 16:32:05 -040040import time
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050041
John Asmuth864311d2014-04-24 15:46:08 -040042from googleapiclient.discovery import build
43from googleapiclient.errors import BatchError
44from googleapiclient.errors import HttpError
45from googleapiclient.errors import InvalidChunkSizeError
Igor Maravić22435292017-01-19 22:28:22 +010046from googleapiclient.http import build_http
John Asmuth864311d2014-04-24 15:46:08 -040047from googleapiclient.http import BatchHttpRequest
48from googleapiclient.http import HttpMock
49from googleapiclient.http import HttpMockSequence
50from googleapiclient.http import HttpRequest
51from googleapiclient.http import MAX_URI_LENGTH
52from googleapiclient.http import MediaFileUpload
53from googleapiclient.http import MediaInMemoryUpload
54from googleapiclient.http import MediaIoBaseDownload
55from googleapiclient.http import MediaIoBaseUpload
56from googleapiclient.http import MediaUpload
57from googleapiclient.http import _StreamSlice
58from googleapiclient.http import set_user_agent
59from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050060from oauth2client.client import Credentials
61
62
63class MockCredentials(Credentials):
64 """Mock class for all Credentials objects."""
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070065 def __init__(self, bearer_token, expired=False):
Joe Gregorio654f4a22012-02-09 14:15:44 -050066 super(MockCredentials, self).__init__()
67 self._authorized = 0
68 self._refreshed = 0
69 self._applied = 0
70 self._bearer_token = bearer_token
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070071 self._access_token_expired = expired
72
73 @property
74 def access_token_expired(self):
75 return self._access_token_expired
Joe Gregorio654f4a22012-02-09 14:15:44 -050076
77 def authorize(self, http):
78 self._authorized += 1
79
80 request_orig = http.request
81
82 # The closure that will replace 'httplib2.Http.request'.
83 def new_request(uri, method='GET', body=None, headers=None,
84 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
85 connection_type=None):
86 # Modify the request headers to add the appropriate
87 # Authorization header.
88 if headers is None:
89 headers = {}
90 self.apply(headers)
91
92 resp, content = request_orig(uri, method, body, headers,
93 redirections, connection_type)
94
95 return resp, content
96
97 # Replace the request method with our own closure.
98 http.request = new_request
99
100 # Set credentials as a property of the request method.
101 setattr(http.request, 'credentials', self)
102
103 return http
104
105 def refresh(self, http):
106 self._refreshed += 1
107
108 def apply(self, headers):
109 self._applied += 1
110 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500111
112
eesheeshc6425a02016-02-12 15:07:06 +0000113class HttpMockWithErrors(object):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100114 def __init__(self, num_errors, success_json, success_data):
115 self.num_errors = num_errors
116 self.success_json = success_json
117 self.success_data = success_data
118
119 def request(self, *args, **kwargs):
120 if not self.num_errors:
121 return httplib2.Response(self.success_json), self.success_data
122 else:
123 self.num_errors -= 1
eesheeshc6425a02016-02-12 15:07:06 +0000124 if self.num_errors == 1:
125 raise ssl.SSLError()
126 else:
127 if PY3:
128 ex = TimeoutError()
129 else:
130 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200131
132 if self.num_errors == 2:
133 #first try a broken pipe error (#218)
134 ex.errno = socket.errno.EPIPE
135 else:
136 # Initialize the timeout error code to the platform's error code.
137 try:
138 # For Windows:
139 ex.errno = socket.errno.WSAETIMEDOUT
140 except AttributeError:
141 # For Linux/Mac:
142 ex.errno = socket.errno.ETIMEDOUT
143 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000144 raise ex
145
146
147class HttpMockWithNonRetriableErrors(object):
148 def __init__(self, num_errors, success_json, success_data):
149 self.num_errors = num_errors
150 self.success_json = success_json
151 self.success_data = success_data
152
153 def request(self, *args, **kwargs):
154 if not self.num_errors:
155 return httplib2.Response(self.success_json), self.success_data
156 else:
157 self.num_errors -= 1
158 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200159 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000160 try:
161 # For Windows:
162 ex.errno = socket.errno.WSAECONNREFUSED
163 except AttributeError:
164 # For Linux/Mac:
165 ex.errno = socket.errno.ECONNREFUSED
166 # Now raise the correct timeout error.
167 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100168
169
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500170DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
171
172
173def datafile(filename):
174 return os.path.join(DATA_DIR, filename)
175
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500176class TestUserAgent(unittest.TestCase):
177
178 def test_set_user_agent(self):
179 http = HttpMockSequence([
180 ({'status': '200'}, 'echo_request_headers'),
181 ])
182
183 http = set_user_agent(http, "my_app/5.5")
184 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500185 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500186
187 def test_set_user_agent_nested(self):
188 http = HttpMockSequence([
189 ({'status': '200'}, 'echo_request_headers'),
190 ])
191
192 http = set_user_agent(http, "my_app/5.5")
193 http = set_user_agent(http, "my_library/0.1")
194 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500195 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500196
Joe Gregorio910b9b12012-06-12 09:36:30 -0400197
198class TestMediaUpload(unittest.TestCase):
199
Nam T. Nguyendc136312015-12-01 10:18:56 -0800200 def test_media_file_upload_mimetype_detection(self):
201 upload = MediaFileUpload(datafile('small.png'))
202 self.assertEqual('image/png', upload.mimetype())
203
204 upload = MediaFileUpload(datafile('empty'))
205 self.assertEqual('application/octet-stream', upload.mimetype())
206
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500207 def test_media_file_upload_to_from_json(self):
208 upload = MediaFileUpload(
209 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500210 self.assertEqual('image/png', upload.mimetype())
211 self.assertEqual(190, upload.size())
212 self.assertEqual(True, upload.resumable())
213 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800214 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500215
216 json = upload.to_json()
217 new_upload = MediaUpload.new_from_json(json)
218
Joe Gregorio654f4a22012-02-09 14:15:44 -0500219 self.assertEqual('image/png', new_upload.mimetype())
220 self.assertEqual(190, new_upload.size())
221 self.assertEqual(True, new_upload.resumable())
222 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800223 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500224
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400225 def test_media_file_upload_raises_on_invalid_chunksize(self):
226 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
227 datafile('small.png'), mimetype='image/png', chunksize=-2,
228 resumable=True)
229
Ali Afshar1cb6b672012-03-12 08:46:14 -0400230 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800231 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400232 resumable=True)
233 self.assertEqual('text/plain', media.mimetype())
234 self.assertEqual(10, media.chunksize())
235 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800236 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400237 self.assertEqual(6, media.size())
238
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500239 def test_http_request_to_from_json(self):
240
241 def _postproc(*kwargs):
242 pass
243
Igor Maravić22435292017-01-19 22:28:22 +0100244 http = build_http()
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500245 media_upload = MediaFileUpload(
246 datafile('small.png'), chunksize=500, resumable=True)
247 req = HttpRequest(
248 http,
249 _postproc,
250 'http://example.com',
251 method='POST',
252 body='{}',
253 headers={'content-type': 'multipart/related; boundary="---flubber"'},
254 methodId='foo',
255 resumable=media_upload)
256
257 json = req.to_json()
258 new_req = HttpRequest.from_json(json, http, _postproc)
259
Joe Gregorio654f4a22012-02-09 14:15:44 -0500260 self.assertEqual({'content-type':
261 'multipart/related; boundary="---flubber"'},
262 new_req.headers)
263 self.assertEqual('http://example.com', new_req.uri)
264 self.assertEqual('{}', new_req.body)
265 self.assertEqual(http, new_req.http)
266 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500267
Joe Gregorio9086bd32013-06-14 16:32:05 -0400268 self.assertEqual(random.random, new_req._rand)
269 self.assertEqual(time.sleep, new_req._sleep)
270
Joe Gregorio910b9b12012-06-12 09:36:30 -0400271
272class TestMediaIoBaseUpload(unittest.TestCase):
273
274 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800275 fd = FileIO(datafile('small.png'), 'r')
276 upload = MediaIoBaseUpload(
277 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
278 self.assertEqual('image/png', upload.mimetype())
279 self.assertEqual(190, upload.size())
280 self.assertEqual(True, upload.resumable())
281 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800282 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400283
284 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800285 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400286 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400287 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400288 self.assertEqual('image/png', upload.mimetype())
289 self.assertEqual(190, upload.size())
290 self.assertEqual(True, upload.resumable())
291 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800292 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400293 f.close()
294
295 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800296 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400297 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400298
299 try:
300 json = upload.to_json()
301 self.fail('MediaIoBaseUpload should not be serializable.')
302 except NotImplementedError:
303 pass
304
Pat Feratec6050872015-03-03 18:24:59 -0800305 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400306 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800307 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800308 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400309 f.close()
310
311 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400312 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400313 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400314 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400315 self.assertEqual(True, upload.resumable())
316 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800317 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400318 f.close()
319
320 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800321 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800322 fd = BytesIO(f.read())
323 upload = MediaIoBaseUpload(
324 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
325 self.assertEqual('image/png', upload.mimetype())
326 self.assertEqual(190, upload.size())
327 self.assertEqual(True, upload.resumable())
328 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800329 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400330
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400331 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800332 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800333 fd = BytesIO(f.read())
334 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
335 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400336
337 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800338 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800339 upload = MediaIoBaseUpload(
340 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
341 self.assertEqual(True, upload.has_stream())
342 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400343
Joe Gregorio9086bd32013-06-14 16:32:05 -0400344 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800345 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800346 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400347 upload = MediaIoBaseUpload(
348 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
349
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500350 # Simulate errors for both the request that creates the resumable upload
351 # and the upload itself.
Joe Gregorio9086bd32013-06-14 16:32:05 -0400352 http = HttpMockSequence([
353 ({'status': '500'}, ''),
354 ({'status': '500'}, ''),
355 ({'status': '503'}, ''),
356 ({'status': '200', 'location': 'location'}, ''),
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500357 ({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
358 ({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE),
359 ({'status': '429'}, ''),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400360 ({'status': '200'}, '{}'),
361 ])
362
363 model = JsonModel()
364 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
365 method = u'POST'
366 request = HttpRequest(
367 http,
368 model.response,
369 uri,
370 method=method,
371 headers={},
372 resumable=upload)
373
374 sleeptimes = []
375 request._sleep = lambda x: sleeptimes.append(x)
376 request._rand = lambda: 10
377
378 request.execute(num_retries=3)
379 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
380
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500381 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
382 fd = BytesIO(b"i am png")
383 upload = MediaIoBaseUpload(
384 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
385
386 http = HttpMockSequence([
387 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
388 ({'status': '200'}, '{}')
389 ])
390
391 model = JsonModel()
392 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
393 method = u'POST'
394 request = HttpRequest(
395 http,
396 model.response,
397 uri,
398 method=method,
399 headers={},
400 resumable=upload)
401
402 request._rand = lambda: 1.0
403 request._sleep = mock.MagicMock()
404
405 with self.assertRaises(HttpError):
406 request.execute(num_retries=3)
407 request._sleep.assert_not_called()
408
Joe Gregorio910b9b12012-06-12 09:36:30 -0400409
Joe Gregorio708388c2012-06-15 13:43:04 -0400410class TestMediaIoBaseDownload(unittest.TestCase):
411
412 def setUp(self):
413 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400414 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400415 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800416 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400417
418 def test_media_io_base_download(self):
419 self.request.http = HttpMockSequence([
420 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800421 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400422 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800423 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400424 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400425 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400426
427 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400428 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400429
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400430 self.assertEqual(self.fd, download._fd)
431 self.assertEqual(3, download._chunksize)
432 self.assertEqual(0, download._progress)
433 self.assertEqual(None, download._total_size)
434 self.assertEqual(False, download._done)
435 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400436
437 status, done = download.next_chunk()
438
Pat Ferate2b140222015-03-03 18:05:11 -0800439 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400440 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400441 self.assertEqual(3, download._progress)
442 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400443 self.assertEqual(3, status.resumable_progress)
444
445 status, done = download.next_chunk()
446
Pat Ferate2b140222015-03-03 18:05:11 -0800447 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400448 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400449 self.assertEqual(5, download._progress)
450 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400451
452 def test_media_io_base_download_handle_redirects(self):
453 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400454 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800455 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400456 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800457 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400458 ])
459
460 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400461 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400462
463 status, done = download.next_chunk()
464
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400465 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400466
467 def test_media_io_base_download_handle_4xx(self):
468 self.request.http = HttpMockSequence([
469 ({'status': '400'}, ''),
470 ])
471
472 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400473 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400474
475 try:
476 status, done = download.next_chunk()
477 self.fail('Should raise an exception')
478 except HttpError:
479 pass
480
481 # Even after raising an exception we can pick up where we left off.
482 self.request.http = HttpMockSequence([
483 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800484 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400485 ])
486
487 status, done = download.next_chunk()
488
Pat Ferate2b140222015-03-03 18:05:11 -0800489 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400490
eesheeshc6425a02016-02-12 15:07:06 +0000491 def test_media_io_base_download_retries_connection_errors(self):
492 self.request.http = HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100493 3, {'status': '200', 'content-range': '0-2/3'}, b'123')
494
495 download = MediaIoBaseDownload(
496 fd=self.fd, request=self.request, chunksize=3)
497 download._sleep = lambda _x: 0 # do nothing
498 download._rand = lambda: 10
499
500 status, done = download.next_chunk(num_retries=3)
501
502 self.assertEqual(self.fd.getvalue(), b'123')
503 self.assertEqual(True, done)
504
Joe Gregorio9086bd32013-06-14 16:32:05 -0400505 def test_media_io_base_download_retries_5xx(self):
506 self.request.http = HttpMockSequence([
507 ({'status': '500'}, ''),
508 ({'status': '500'}, ''),
509 ({'status': '500'}, ''),
510 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800511 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400512 ({'status': '503'}, ''),
513 ({'status': '503'}, ''),
514 ({'status': '503'}, ''),
515 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800516 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400517 ])
518
519 download = MediaIoBaseDownload(
520 fd=self.fd, request=self.request, chunksize=3)
521
522 self.assertEqual(self.fd, download._fd)
523 self.assertEqual(3, download._chunksize)
524 self.assertEqual(0, download._progress)
525 self.assertEqual(None, download._total_size)
526 self.assertEqual(False, download._done)
527 self.assertEqual(self.request.uri, download._uri)
528
529 # Set time.sleep and random.random stubs.
530 sleeptimes = []
531 download._sleep = lambda x: sleeptimes.append(x)
532 download._rand = lambda: 10
533
534 status, done = download.next_chunk(num_retries=3)
535
536 # Check for exponential backoff using the rand function above.
537 self.assertEqual([20, 40, 80], sleeptimes)
538
Pat Ferate2b140222015-03-03 18:05:11 -0800539 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400540 self.assertEqual(False, done)
541 self.assertEqual(3, download._progress)
542 self.assertEqual(5, download._total_size)
543 self.assertEqual(3, status.resumable_progress)
544
545 # Reset time.sleep stub.
546 del sleeptimes[0:len(sleeptimes)]
547
548 status, done = download.next_chunk(num_retries=3)
549
550 # Check for exponential backoff using the rand function above.
551 self.assertEqual([20, 40, 80], sleeptimes)
552
Pat Ferate2b140222015-03-03 18:05:11 -0800553 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400554 self.assertEqual(True, done)
555 self.assertEqual(5, download._progress)
556 self.assertEqual(5, download._total_size)
557
andrewnestera4a44cf2017-03-31 16:09:31 +0300558 def test_media_io_base_download_empty_file(self):
559 self.request.http = HttpMockSequence([
560 ({'status': '200',
561 'content-range': '0-0/0'}, b''),
562 ])
563
564 download = MediaIoBaseDownload(
565 fd=self.fd, request=self.request, chunksize=3)
566
567 self.assertEqual(self.fd, download._fd)
568 self.assertEqual(0, download._progress)
569 self.assertEqual(None, download._total_size)
570 self.assertEqual(False, download._done)
571 self.assertEqual(self.request.uri, download._uri)
572
573 status, done = download.next_chunk()
574
575 self.assertEqual(True, done)
576 self.assertEqual(0, download._progress)
577 self.assertEqual(0, download._total_size)
578 self.assertEqual(0, status.progress())
579
Joe Gregorio66f57522011-11-30 11:00:00 -0500580EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
581Content-Type: application/json
582MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500583Host: www.googleapis.com
584content-length: 2\r\n\r\n{}"""
585
586
587NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
588Content-Type: application/json
589MIME-Version: 1.0
590Host: www.googleapis.com
591content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500592
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400593NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
594Content-Type: application/json
595MIME-Version: 1.0
596Host: www.googleapis.com\r\n\r\n"""
597
Joe Gregorio66f57522011-11-30 11:00:00 -0500598
599RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400600Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500601Content-Length: 14
602ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
603
604
INADA Naoki09157612015-03-25 01:51:03 +0900605BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500606Content-Type: application/http
607Content-Transfer-Encoding: binary
608Content-ID: <randomness+1>
609
610HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400611Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500612Content-Length: 14
613ETag: "etag/pony"\r\n\r\n{"foo": 42}
614
615--batch_foobarbaz
616Content-Type: application/http
617Content-Transfer-Encoding: binary
618Content-ID: <randomness+2>
619
620HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400621Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500622Content-Length: 14
623ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
624--batch_foobarbaz--"""
625
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500626
INADA Naoki09157612015-03-25 01:51:03 +0900627BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400628Content-Type: application/http
629Content-Transfer-Encoding: binary
630Content-ID: <randomness+1>
631
632HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400633Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400634Content-Length: 14
635ETag: "etag/pony"\r\n\r\n{"foo": 42}
636
637--batch_foobarbaz
638Content-Type: application/http
639Content-Transfer-Encoding: binary
640Content-ID: <randomness+2>
641
642HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400643Content-Type: application/json
644Content-Length: 245
645ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400646 "error": {
647 "errors": [
648 {
649 "domain": "usageLimits",
650 "reason": "accessNotConfigured",
651 "message": "Access Not Configured",
652 "debugInfo": "QuotaState: BLOCKED"
653 }
654 ],
655 "code": 403,
656 "message": "Access Not Configured"
657 }
658}
659
660--batch_foobarbaz--"""
661
662
INADA Naoki09157612015-03-25 01:51:03 +0900663BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500664Content-Type: application/http
665Content-Transfer-Encoding: binary
666Content-ID: <randomness+1>
667
Joe Gregorioc752e332012-07-11 14:43:52 -0400668HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400669Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500670Content-Length: 14
671ETag: "etag/pony"\r\n\r\n{"error": {"message":
672 "Authorizaton failed."}}
673
674--batch_foobarbaz
675Content-Type: application/http
676Content-Transfer-Encoding: binary
677Content-ID: <randomness+2>
678
679HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400680Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500681Content-Length: 14
682ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
683--batch_foobarbaz--"""
684
685
INADA Naoki09157612015-03-25 01:51:03 +0900686BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500687Content-Type: application/http
688Content-Transfer-Encoding: binary
689Content-ID: <randomness+1>
690
691HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400692Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500693Content-Length: 14
694ETag: "etag/pony"\r\n\r\n{"foo": 42}
695--batch_foobarbaz--"""
696
eesheeshc6425a02016-02-12 15:07:06 +0000697
698USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
699 "error": {
700 "errors": [
701 {
702 "domain": "usageLimits",
703 "reason": "userRateLimitExceeded",
704 "message": "User Rate Limit Exceeded"
705 }
706 ],
707 "code": 403,
708 "message": "User Rate Limit Exceeded"
709 }
710}"""
711
712
713RATE_LIMIT_EXCEEDED_RESPONSE = """{
714 "error": {
715 "errors": [
716 {
717 "domain": "usageLimits",
718 "reason": "rateLimitExceeded",
719 "message": "Rate Limit Exceeded"
720 }
721 ],
722 "code": 403,
723 "message": "Rate Limit Exceeded"
724 }
725}"""
726
727
728NOT_CONFIGURED_RESPONSE = """{
729 "error": {
730 "errors": [
731 {
732 "domain": "usageLimits",
733 "reason": "accessNotConfigured",
734 "message": "Access Not Configured"
735 }
736 ],
737 "code": 403,
738 "message": "Access Not Configured"
739 }
740}"""
741
Joe Gregorio654f4a22012-02-09 14:15:44 -0500742class Callbacks(object):
743 def __init__(self):
744 self.responses = {}
745 self.exceptions = {}
746
747 def f(self, request_id, response, exception):
748 self.responses[request_id] = response
749 self.exceptions[request_id] = exception
750
751
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500752class TestHttpRequest(unittest.TestCase):
753 def test_unicode(self):
754 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
755 model = JsonModel()
756 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
757 method = u'POST'
758 request = HttpRequest(
759 http,
760 model.response,
761 uri,
762 method=method,
763 body=u'{}',
764 headers={'content-type': 'application/json'})
765 request.execute()
766 self.assertEqual(uri, http.uri)
767 self.assertEqual(str, type(http.uri))
768 self.assertEqual(method, http.method)
769 self.assertEqual(str, type(http.method))
770
eesheeshc6425a02016-02-12 15:07:06 +0000771 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100772 model = JsonModel()
773 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000774 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
775 model.response,
776 u'https://www.example.com/json_api_endpoint')
777 request._sleep = lambda _x: 0 # do nothing
778 request._rand = lambda: 10
779 with self.assertRaises(socket.error):
780 response = request.execute(num_retries=3)
781
782
783 def test_retry_connection_errors_non_resumable(self):
784 model = JsonModel()
785 request = HttpRequest(
786 HttpMockWithErrors(3, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100787 model.response,
788 u'https://www.example.com/json_api_endpoint')
789 request._sleep = lambda _x: 0 # do nothing
790 request._rand = lambda: 10
791 response = request.execute(num_retries=3)
792 self.assertEqual({u'foo': u'bar'}, response)
793
eesheeshc6425a02016-02-12 15:07:06 +0000794 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100795 with open(datafile('small.png'), 'rb') as small_png_file:
796 small_png_fd = BytesIO(small_png_file.read())
797 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
798 chunksize=500, resumable=True)
799 model = JsonModel()
800
801 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000802 HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100803 3, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
804 model.response,
805 u'https://www.example.com/file_upload',
806 method='POST',
807 resumable=upload)
808 request._sleep = lambda _x: 0 # do nothing
809 request._rand = lambda: 10
810 response = request.execute(num_retries=3)
811 self.assertEqual({u'foo': u'bar'}, response)
812
Joe Gregorio9086bd32013-06-14 16:32:05 -0400813 def test_retry(self):
814 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000815 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
816 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
817 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
818 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400819 resp_seq.append(({'status': '200'}, '{}'))
820
821 http = HttpMockSequence(resp_seq)
822 model = JsonModel()
823 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
824 method = u'POST'
825 request = HttpRequest(
826 http,
827 model.response,
828 uri,
829 method=method,
830 body=u'{}',
831 headers={'content-type': 'application/json'})
832
833 sleeptimes = []
834 request._sleep = lambda x: sleeptimes.append(x)
835 request._rand = lambda: 10
836
837 request.execute(num_retries=num_retries)
838
839 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900840 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400841 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
842
eesheeshc6425a02016-02-12 15:07:06 +0000843 def test_no_retry_succeeds(self):
844 num_retries = 5
845 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
846
847 http = HttpMockSequence(resp_seq)
848 model = JsonModel()
849 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
850 method = u'POST'
851 request = HttpRequest(
852 http,
853 model.response,
854 uri,
855 method=method,
856 body=u'{}',
857 headers={'content-type': 'application/json'})
858
859 sleeptimes = []
860 request._sleep = lambda x: sleeptimes.append(x)
861 request._rand = lambda: 10
862
863 request.execute(num_retries=num_retries)
864
865 self.assertEqual(0, len(sleeptimes))
866
Joe Gregorio9086bd32013-06-14 16:32:05 -0400867 def test_no_retry_fails_fast(self):
868 http = HttpMockSequence([
869 ({'status': '500'}, ''),
870 ({'status': '200'}, '{}')
871 ])
872 model = JsonModel()
873 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
874 method = u'POST'
875 request = HttpRequest(
876 http,
877 model.response,
878 uri,
879 method=method,
880 body=u'{}',
881 headers={'content-type': 'application/json'})
882
883 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000884 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400885
eesheeshc6425a02016-02-12 15:07:06 +0000886 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400887 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000888 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400889
eesheeshc6425a02016-02-12 15:07:06 +0000890 def test_no_retry_403_not_configured_fails_fast(self):
891 http = HttpMockSequence([
892 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
893 ({'status': '200'}, '{}')
894 ])
895 model = JsonModel()
896 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
897 method = u'POST'
898 request = HttpRequest(
899 http,
900 model.response,
901 uri,
902 method=method,
903 body=u'{}',
904 headers={'content-type': 'application/json'})
905
906 request._rand = lambda: 1.0
907 request._sleep = mock.MagicMock()
908
909 with self.assertRaises(HttpError):
910 request.execute()
911 request._sleep.assert_not_called()
912
913 def test_no_retry_403_fails_fast(self):
914 http = HttpMockSequence([
915 ({'status': '403'}, ''),
916 ({'status': '200'}, '{}')
917 ])
918 model = JsonModel()
919 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
920 method = u'POST'
921 request = HttpRequest(
922 http,
923 model.response,
924 uri,
925 method=method,
926 body=u'{}',
927 headers={'content-type': 'application/json'})
928
929 request._rand = lambda: 1.0
930 request._sleep = mock.MagicMock()
931
932 with self.assertRaises(HttpError):
933 request.execute()
934 request._sleep.assert_not_called()
935
936 def test_no_retry_401_fails_fast(self):
937 http = HttpMockSequence([
938 ({'status': '401'}, ''),
939 ({'status': '200'}, '{}')
940 ])
941 model = JsonModel()
942 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
943 method = u'POST'
944 request = HttpRequest(
945 http,
946 model.response,
947 uri,
948 method=method,
949 body=u'{}',
950 headers={'content-type': 'application/json'})
951
952 request._rand = lambda: 1.0
953 request._sleep = mock.MagicMock()
954
955 with self.assertRaises(HttpError):
956 request.execute()
957 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500958
Joe Gregorio66f57522011-11-30 11:00:00 -0500959class TestBatch(unittest.TestCase):
960
961 def setUp(self):
962 model = JsonModel()
963 self.request1 = HttpRequest(
964 None,
965 model.response,
966 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
967 method='POST',
968 body='{}',
969 headers={'content-type': 'application/json'})
970
971 self.request2 = HttpRequest(
972 None,
973 model.response,
974 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500975 method='GET',
976 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500977 headers={'content-type': 'application/json'})
978
979
980 def test_id_to_from_content_id_header(self):
981 batch = BatchHttpRequest()
982 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
983
984 def test_invalid_content_id_header(self):
985 batch = BatchHttpRequest()
986 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
987 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
988 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
989
990 def test_serialize_request(self):
991 batch = BatchHttpRequest()
992 request = HttpRequest(
993 None,
994 None,
995 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
996 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800997 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -0500998 headers={'content-type': 'application/json'},
999 methodId=None,
1000 resumable=None)
1001 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001002 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001003
Joe Gregoriodd813822012-01-25 10:32:47 -05001004 def test_serialize_request_media_body(self):
1005 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001006 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001007 body = f.read()
1008 f.close()
1009
1010 request = HttpRequest(
1011 None,
1012 None,
1013 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1014 method='POST',
1015 body=body,
1016 headers={'content-type': 'application/json'},
1017 methodId=None,
1018 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001019 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001020 s = batch._serialize_request(request).splitlines()
1021
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001022 def test_serialize_request_no_body(self):
1023 batch = BatchHttpRequest()
1024 request = HttpRequest(
1025 None,
1026 None,
1027 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1028 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001029 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001030 headers={'content-type': 'application/json'},
1031 methodId=None,
1032 resumable=None)
1033 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001034 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001035
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001036 def test_serialize_get_request_no_body(self):
1037 batch = BatchHttpRequest()
1038 request = HttpRequest(
1039 None,
1040 None,
1041 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1042 method='GET',
1043 body=None,
1044 headers={'content-type': 'application/json'},
1045 methodId=None,
1046 resumable=None)
1047 s = batch._serialize_request(request).splitlines()
1048 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1049
Joe Gregorio66f57522011-11-30 11:00:00 -05001050 def test_deserialize_response(self):
1051 batch = BatchHttpRequest()
1052 resp, content = batch._deserialize_response(RESPONSE)
1053
Joe Gregorio654f4a22012-02-09 14:15:44 -05001054 self.assertEqual(200, resp.status)
1055 self.assertEqual('OK', resp.reason)
1056 self.assertEqual(11, resp.version)
1057 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001058
1059 def test_new_id(self):
1060 batch = BatchHttpRequest()
1061
1062 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001063 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001064
1065 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001066 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001067
1068 batch.add(self.request1, request_id='3')
1069
1070 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001071 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001072
1073 def test_add(self):
1074 batch = BatchHttpRequest()
1075 batch.add(self.request1, request_id='1')
1076 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1077
1078 def test_add_fail_for_resumable(self):
1079 batch = BatchHttpRequest()
1080
1081 upload = MediaFileUpload(
1082 datafile('small.png'), chunksize=500, resumable=True)
1083 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001084 with self.assertRaises(BatchError) as batch_error:
1085 batch.add(self.request1, request_id='1')
1086 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001087
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001088 def test_execute_empty_batch_no_http(self):
1089 batch = BatchHttpRequest()
1090 ret = batch.execute()
1091 self.assertEqual(None, ret)
1092
Joe Gregorio66f57522011-11-30 11:00:00 -05001093 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001094 batch = BatchHttpRequest()
1095 callbacks = Callbacks()
1096
1097 batch.add(self.request1, callback=callbacks.f)
1098 batch.add(self.request2, callback=callbacks.f)
1099 http = HttpMockSequence([
1100 ({'status': '200',
1101 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1102 BATCH_RESPONSE),
1103 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001104 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001105 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1106 self.assertEqual(None, callbacks.exceptions['1'])
1107 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1108 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001109
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001110 def test_execute_request_body(self):
1111 batch = BatchHttpRequest()
1112
1113 batch.add(self.request1)
1114 batch.add(self.request2)
1115 http = HttpMockSequence([
1116 ({'status': '200',
1117 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1118 'echo_request_body'),
1119 ])
1120 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001121 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001122 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001123 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001124 boundary, _ = e.content.split(None, 1)
1125 self.assertEqual('--', boundary[:2])
1126 parts = e.content.split(boundary)
1127 self.assertEqual(4, len(parts))
1128 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001129 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001130 header = parts[1].splitlines()[1]
1131 self.assertEqual('Content-Type: application/http', header)
1132
Gabriel Garcia23174be2016-05-25 17:28:07 +02001133 def test_execute_initial_refresh_oauth2(self):
1134 batch = BatchHttpRequest()
1135 callbacks = Callbacks()
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -07001136 cred = MockCredentials('Foo', expired=True)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001137
1138 http = HttpMockSequence([
1139 ({'status': '200',
1140 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1141 BATCH_SINGLE_RESPONSE),
1142 ])
1143
1144 cred.authorize(http)
1145
1146 batch.add(self.request1, callback=callbacks.f)
1147 batch.execute(http=http)
1148
1149 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1150 self.assertIsNone(callbacks.exceptions['1'])
1151
1152 self.assertEqual(1, cred._refreshed)
1153
1154 self.assertEqual(1, cred._authorized)
1155
1156 self.assertEqual(1, cred._applied)
1157
Joe Gregorio654f4a22012-02-09 14:15:44 -05001158 def test_execute_refresh_and_retry_on_401(self):
1159 batch = BatchHttpRequest()
1160 callbacks = Callbacks()
1161 cred_1 = MockCredentials('Foo')
1162 cred_2 = MockCredentials('Bar')
1163
1164 http = HttpMockSequence([
1165 ({'status': '200',
1166 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1167 BATCH_RESPONSE_WITH_401),
1168 ({'status': '200',
1169 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1170 BATCH_SINGLE_RESPONSE),
1171 ])
1172
1173 creds_http_1 = HttpMockSequence([])
1174 cred_1.authorize(creds_http_1)
1175
1176 creds_http_2 = HttpMockSequence([])
1177 cred_2.authorize(creds_http_2)
1178
1179 self.request1.http = creds_http_1
1180 self.request2.http = creds_http_2
1181
1182 batch.add(self.request1, callback=callbacks.f)
1183 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001184 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001185
1186 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1187 self.assertEqual(None, callbacks.exceptions['1'])
1188 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1189 self.assertEqual(None, callbacks.exceptions['2'])
1190
1191 self.assertEqual(1, cred_1._refreshed)
1192 self.assertEqual(0, cred_2._refreshed)
1193
1194 self.assertEqual(1, cred_1._authorized)
1195 self.assertEqual(1, cred_2._authorized)
1196
1197 self.assertEqual(1, cred_2._applied)
1198 self.assertEqual(2, cred_1._applied)
1199
1200 def test_http_errors_passed_to_callback(self):
1201 batch = BatchHttpRequest()
1202 callbacks = Callbacks()
1203 cred_1 = MockCredentials('Foo')
1204 cred_2 = MockCredentials('Bar')
1205
1206 http = HttpMockSequence([
1207 ({'status': '200',
1208 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1209 BATCH_RESPONSE_WITH_401),
1210 ({'status': '200',
1211 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1212 BATCH_RESPONSE_WITH_401),
1213 ])
1214
1215 creds_http_1 = HttpMockSequence([])
1216 cred_1.authorize(creds_http_1)
1217
1218 creds_http_2 = HttpMockSequence([])
1219 cred_2.authorize(creds_http_2)
1220
1221 self.request1.http = creds_http_1
1222 self.request2.http = creds_http_2
1223
1224 batch.add(self.request1, callback=callbacks.f)
1225 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001226 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001227
1228 self.assertEqual(None, callbacks.responses['1'])
1229 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001230 self.assertEqual(
1231 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001232 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1233 self.assertEqual(None, callbacks.exceptions['2'])
1234
Joe Gregorio66f57522011-11-30 11:00:00 -05001235 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001236 callbacks = Callbacks()
1237 batch = BatchHttpRequest(callback=callbacks.f)
1238
1239 batch.add(self.request1)
1240 batch.add(self.request2)
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({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001249
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001250 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001251 callbacks = Callbacks()
1252 batch = BatchHttpRequest(callback=callbacks.f)
1253
1254 batch.add(self.request1)
1255 batch.add(self.request2)
1256 http = HttpMockSequence([
1257 ({'status': '200',
1258 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1259 BATCH_ERROR_RESPONSE),
1260 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001261 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001262 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1263 expected = ('<HttpError 403 when requesting '
1264 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1265 '"Access Not Configured">')
1266 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001267
Joe Gregorio5c120db2012-08-23 09:13:55 -04001268
Joe Gregorioba5c7902012-08-03 12:48:16 -04001269class TestRequestUriTooLong(unittest.TestCase):
1270
1271 def test_turn_get_into_post(self):
1272
1273 def _postproc(resp, content):
1274 return content
1275
1276 http = HttpMockSequence([
1277 ({'status': '200'},
1278 'echo_request_body'),
1279 ({'status': '200'},
1280 'echo_request_headers'),
1281 ])
1282
1283 # Send a long query parameter.
1284 query = {
1285 'q': 'a' * MAX_URI_LENGTH + '?&'
1286 }
1287 req = HttpRequest(
1288 http,
1289 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001290 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001291 method='GET',
1292 body=None,
1293 headers={},
1294 methodId='foo',
1295 resumable=None)
1296
1297 # Query parameters should be sent in the body.
1298 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001299 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001300
1301 # Extra headers should be set.
1302 response = req.execute()
1303 self.assertEqual('GET', response['x-http-method-override'])
1304 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1305 self.assertEqual(
1306 'application/x-www-form-urlencoded', response['content-type'])
1307
Joe Gregorio5c120db2012-08-23 09:13:55 -04001308
1309class TestStreamSlice(unittest.TestCase):
1310 """Test _StreamSlice."""
1311
1312 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001313 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001314
1315 def test_read(self):
1316 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001317 self.assertEqual(b'', s.read(0))
1318 self.assertEqual(b'0', s.read(1))
1319 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001320
1321 def test_read_too_much(self):
1322 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001323 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001324
1325 def test_read_all(self):
1326 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001327 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001328
Ali Afshar164f37e2013-01-07 14:05:45 -08001329
1330class TestResponseCallback(unittest.TestCase):
1331 """Test adding callbacks to responses."""
1332
1333 def test_ensure_response_callback(self):
1334 m = JsonModel()
1335 request = HttpRequest(
1336 None,
1337 m.response,
1338 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1339 method='POST',
1340 body='{}',
1341 headers={'content-type': 'application/json'})
1342 h = HttpMockSequence([ ({'status': 200}, '{}')])
1343 responses = []
1344 def _on_response(resp, responses=responses):
1345 responses.append(resp)
1346 request.add_response_callback(_on_response)
1347 request.execute(http=h)
1348 self.assertEqual(1, len(responses))
1349
1350
Craig Gurnik8e55b762015-01-20 15:00:10 -05001351class TestHttpMock(unittest.TestCase):
1352 def test_default_response_headers(self):
1353 http = HttpMock(datafile('zoo.json'))
1354 resp, content = http.request("http://example.com")
1355 self.assertEqual(resp.status, 200)
1356
Alan Briolat26b01002015-08-14 00:13:57 +01001357 def test_error_response(self):
1358 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1359 model = JsonModel()
1360 request = HttpRequest(
1361 http,
1362 model.response,
1363 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1364 method='GET',
1365 headers={})
1366 self.assertRaises(HttpError, request.execute)
1367
Craig Gurnik8e55b762015-01-20 15:00:10 -05001368
Igor Maravić22435292017-01-19 22:28:22 +01001369class TestHttpBuild(unittest.TestCase):
1370 original_socket_default_timeout = None
1371
1372 @classmethod
1373 def setUpClass(cls):
1374 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1375
1376 @classmethod
1377 def tearDownClass(cls):
1378 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1379
1380 def test_build_http_sets_default_timeout_if_none_specified(self):
1381 socket.setdefaulttimeout(None)
1382 http = build_http()
1383 self.assertIsInstance(http.timeout, int)
1384 self.assertGreater(http.timeout, 0)
1385
1386 def test_build_http_default_timeout_can_be_overridden(self):
1387 socket.setdefaulttimeout(1.5)
1388 http = build_http()
1389 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1390
1391 def test_build_http_default_timeout_can_be_set_to_zero(self):
1392 socket.setdefaulttimeout(0)
1393 http = build_http()
1394 self.assertEquals(http.timeout, 0)
1395
1396
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001397if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001398 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001399 unittest.main()