blob: 1cca7c69db54c58be86ef01f26544533a7fe64eb [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
46from googleapiclient.http import BatchHttpRequest
47from googleapiclient.http import HttpMock
48from googleapiclient.http import HttpMockSequence
49from googleapiclient.http import HttpRequest
50from googleapiclient.http import MAX_URI_LENGTH
51from googleapiclient.http import MediaFileUpload
52from googleapiclient.http import MediaInMemoryUpload
53from googleapiclient.http import MediaIoBaseDownload
54from googleapiclient.http import MediaIoBaseUpload
55from googleapiclient.http import MediaUpload
56from googleapiclient.http import _StreamSlice
57from googleapiclient.http import set_user_agent
58from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050059from oauth2client.client import Credentials
60
61
62class MockCredentials(Credentials):
63 """Mock class for all Credentials objects."""
64 def __init__(self, bearer_token):
65 super(MockCredentials, self).__init__()
66 self._authorized = 0
67 self._refreshed = 0
68 self._applied = 0
69 self._bearer_token = bearer_token
70
71 def authorize(self, http):
72 self._authorized += 1
73
74 request_orig = http.request
75
76 # The closure that will replace 'httplib2.Http.request'.
77 def new_request(uri, method='GET', body=None, headers=None,
78 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
79 connection_type=None):
80 # Modify the request headers to add the appropriate
81 # Authorization header.
82 if headers is None:
83 headers = {}
84 self.apply(headers)
85
86 resp, content = request_orig(uri, method, body, headers,
87 redirections, connection_type)
88
89 return resp, content
90
91 # Replace the request method with our own closure.
92 http.request = new_request
93
94 # Set credentials as a property of the request method.
95 setattr(http.request, 'credentials', self)
96
97 return http
98
99 def refresh(self, http):
100 self._refreshed += 1
101
102 def apply(self, headers):
103 self._applied += 1
104 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500105
106
eesheeshc6425a02016-02-12 15:07:06 +0000107class HttpMockWithErrors(object):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100108 def __init__(self, num_errors, success_json, success_data):
109 self.num_errors = num_errors
110 self.success_json = success_json
111 self.success_data = success_data
112
113 def request(self, *args, **kwargs):
114 if not self.num_errors:
115 return httplib2.Response(self.success_json), self.success_data
116 else:
117 self.num_errors -= 1
eesheeshc6425a02016-02-12 15:07:06 +0000118 if self.num_errors == 1:
119 raise ssl.SSLError()
120 else:
121 if PY3:
122 ex = TimeoutError()
123 else:
124 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200125
126 if self.num_errors == 2:
127 #first try a broken pipe error (#218)
128 ex.errno = socket.errno.EPIPE
129 else:
130 # Initialize the timeout error code to the platform's error code.
131 try:
132 # For Windows:
133 ex.errno = socket.errno.WSAETIMEDOUT
134 except AttributeError:
135 # For Linux/Mac:
136 ex.errno = socket.errno.ETIMEDOUT
137 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000138 raise ex
139
140
141class HttpMockWithNonRetriableErrors(object):
142 def __init__(self, num_errors, success_json, success_data):
143 self.num_errors = num_errors
144 self.success_json = success_json
145 self.success_data = success_data
146
147 def request(self, *args, **kwargs):
148 if not self.num_errors:
149 return httplib2.Response(self.success_json), self.success_data
150 else:
151 self.num_errors -= 1
152 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200153 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000154 try:
155 # For Windows:
156 ex.errno = socket.errno.WSAECONNREFUSED
157 except AttributeError:
158 # For Linux/Mac:
159 ex.errno = socket.errno.ECONNREFUSED
160 # Now raise the correct timeout error.
161 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100162
163
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500164DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
165
166
167def datafile(filename):
168 return os.path.join(DATA_DIR, filename)
169
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500170class TestUserAgent(unittest.TestCase):
171
172 def test_set_user_agent(self):
173 http = HttpMockSequence([
174 ({'status': '200'}, 'echo_request_headers'),
175 ])
176
177 http = set_user_agent(http, "my_app/5.5")
178 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500179 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500180
181 def test_set_user_agent_nested(self):
182 http = HttpMockSequence([
183 ({'status': '200'}, 'echo_request_headers'),
184 ])
185
186 http = set_user_agent(http, "my_app/5.5")
187 http = set_user_agent(http, "my_library/0.1")
188 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500189 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500190
Joe Gregorio910b9b12012-06-12 09:36:30 -0400191
192class TestMediaUpload(unittest.TestCase):
193
Nam T. Nguyendc136312015-12-01 10:18:56 -0800194 def test_media_file_upload_mimetype_detection(self):
195 upload = MediaFileUpload(datafile('small.png'))
196 self.assertEqual('image/png', upload.mimetype())
197
198 upload = MediaFileUpload(datafile('empty'))
199 self.assertEqual('application/octet-stream', upload.mimetype())
200
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500201 def test_media_file_upload_to_from_json(self):
202 upload = MediaFileUpload(
203 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500204 self.assertEqual('image/png', upload.mimetype())
205 self.assertEqual(190, upload.size())
206 self.assertEqual(True, upload.resumable())
207 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800208 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500209
210 json = upload.to_json()
211 new_upload = MediaUpload.new_from_json(json)
212
Joe Gregorio654f4a22012-02-09 14:15:44 -0500213 self.assertEqual('image/png', new_upload.mimetype())
214 self.assertEqual(190, new_upload.size())
215 self.assertEqual(True, new_upload.resumable())
216 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800217 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500218
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400219 def test_media_file_upload_raises_on_invalid_chunksize(self):
220 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
221 datafile('small.png'), mimetype='image/png', chunksize=-2,
222 resumable=True)
223
Ali Afshar1cb6b672012-03-12 08:46:14 -0400224 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800225 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400226 resumable=True)
227 self.assertEqual('text/plain', media.mimetype())
228 self.assertEqual(10, media.chunksize())
229 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800230 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400231 self.assertEqual(6, media.size())
232
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500233 def test_http_request_to_from_json(self):
234
235 def _postproc(*kwargs):
236 pass
237
238 http = httplib2.Http()
239 media_upload = MediaFileUpload(
240 datafile('small.png'), chunksize=500, resumable=True)
241 req = HttpRequest(
242 http,
243 _postproc,
244 'http://example.com',
245 method='POST',
246 body='{}',
247 headers={'content-type': 'multipart/related; boundary="---flubber"'},
248 methodId='foo',
249 resumable=media_upload)
250
251 json = req.to_json()
252 new_req = HttpRequest.from_json(json, http, _postproc)
253
Joe Gregorio654f4a22012-02-09 14:15:44 -0500254 self.assertEqual({'content-type':
255 'multipart/related; boundary="---flubber"'},
256 new_req.headers)
257 self.assertEqual('http://example.com', new_req.uri)
258 self.assertEqual('{}', new_req.body)
259 self.assertEqual(http, new_req.http)
260 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500261
Joe Gregorio9086bd32013-06-14 16:32:05 -0400262 self.assertEqual(random.random, new_req._rand)
263 self.assertEqual(time.sleep, new_req._sleep)
264
Joe Gregorio910b9b12012-06-12 09:36:30 -0400265
266class TestMediaIoBaseUpload(unittest.TestCase):
267
268 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800269 fd = FileIO(datafile('small.png'), 'r')
270 upload = MediaIoBaseUpload(
271 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
272 self.assertEqual('image/png', upload.mimetype())
273 self.assertEqual(190, upload.size())
274 self.assertEqual(True, upload.resumable())
275 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800276 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400277
278 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800279 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400280 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400281 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400282 self.assertEqual('image/png', upload.mimetype())
283 self.assertEqual(190, upload.size())
284 self.assertEqual(True, upload.resumable())
285 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800286 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400287 f.close()
288
289 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800290 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400291 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400292
293 try:
294 json = upload.to_json()
295 self.fail('MediaIoBaseUpload should not be serializable.')
296 except NotImplementedError:
297 pass
298
Pat Feratec6050872015-03-03 18:24:59 -0800299 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400300 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800301 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800302 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400303 f.close()
304
305 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400306 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400307 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400308 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400309 self.assertEqual(True, upload.resumable())
310 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800311 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400312 f.close()
313
314 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800315 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800316 fd = BytesIO(f.read())
317 upload = MediaIoBaseUpload(
318 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
319 self.assertEqual('image/png', upload.mimetype())
320 self.assertEqual(190, upload.size())
321 self.assertEqual(True, upload.resumable())
322 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800323 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400324
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400325 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800326 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800327 fd = BytesIO(f.read())
328 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
329 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400330
331 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800332 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800333 upload = MediaIoBaseUpload(
334 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
335 self.assertEqual(True, upload.has_stream())
336 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400337
Joe Gregorio9086bd32013-06-14 16:32:05 -0400338 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800339 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800340 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400341 upload = MediaIoBaseUpload(
342 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
343
344 # Simulate 5XXs for both the request that creates the resumable upload and
345 # the upload itself.
346 http = HttpMockSequence([
347 ({'status': '500'}, ''),
348 ({'status': '500'}, ''),
349 ({'status': '503'}, ''),
350 ({'status': '200', 'location': 'location'}, ''),
351 ({'status': '500'}, ''),
352 ({'status': '500'}, ''),
353 ({'status': '503'}, ''),
354 ({'status': '200'}, '{}'),
355 ])
356
357 model = JsonModel()
358 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
359 method = u'POST'
360 request = HttpRequest(
361 http,
362 model.response,
363 uri,
364 method=method,
365 headers={},
366 resumable=upload)
367
368 sleeptimes = []
369 request._sleep = lambda x: sleeptimes.append(x)
370 request._rand = lambda: 10
371
372 request.execute(num_retries=3)
373 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
374
Joe Gregorio910b9b12012-06-12 09:36:30 -0400375
Joe Gregorio708388c2012-06-15 13:43:04 -0400376class TestMediaIoBaseDownload(unittest.TestCase):
377
378 def setUp(self):
379 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400380 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400381 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800382 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400383
384 def test_media_io_base_download(self):
385 self.request.http = HttpMockSequence([
386 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800387 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400388 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800389 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400390 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400391 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400392
393 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400394 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400395
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400396 self.assertEqual(self.fd, download._fd)
397 self.assertEqual(3, download._chunksize)
398 self.assertEqual(0, download._progress)
399 self.assertEqual(None, download._total_size)
400 self.assertEqual(False, download._done)
401 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400402
403 status, done = download.next_chunk()
404
Pat Ferate2b140222015-03-03 18:05:11 -0800405 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400406 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400407 self.assertEqual(3, download._progress)
408 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400409 self.assertEqual(3, status.resumable_progress)
410
411 status, done = download.next_chunk()
412
Pat Ferate2b140222015-03-03 18:05:11 -0800413 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400414 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400415 self.assertEqual(5, download._progress)
416 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400417
418 def test_media_io_base_download_handle_redirects(self):
419 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400420 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800421 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400422 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800423 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400424 ])
425
426 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400427 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400428
429 status, done = download.next_chunk()
430
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400431 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400432
433 def test_media_io_base_download_handle_4xx(self):
434 self.request.http = HttpMockSequence([
435 ({'status': '400'}, ''),
436 ])
437
438 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400439 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400440
441 try:
442 status, done = download.next_chunk()
443 self.fail('Should raise an exception')
444 except HttpError:
445 pass
446
447 # Even after raising an exception we can pick up where we left off.
448 self.request.http = HttpMockSequence([
449 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800450 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400451 ])
452
453 status, done = download.next_chunk()
454
Pat Ferate2b140222015-03-03 18:05:11 -0800455 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400456
eesheeshc6425a02016-02-12 15:07:06 +0000457 def test_media_io_base_download_retries_connection_errors(self):
458 self.request.http = HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100459 3, {'status': '200', 'content-range': '0-2/3'}, b'123')
460
461 download = MediaIoBaseDownload(
462 fd=self.fd, request=self.request, chunksize=3)
463 download._sleep = lambda _x: 0 # do nothing
464 download._rand = lambda: 10
465
466 status, done = download.next_chunk(num_retries=3)
467
468 self.assertEqual(self.fd.getvalue(), b'123')
469 self.assertEqual(True, done)
470
Joe Gregorio9086bd32013-06-14 16:32:05 -0400471 def test_media_io_base_download_retries_5xx(self):
472 self.request.http = HttpMockSequence([
473 ({'status': '500'}, ''),
474 ({'status': '500'}, ''),
475 ({'status': '500'}, ''),
476 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800477 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400478 ({'status': '503'}, ''),
479 ({'status': '503'}, ''),
480 ({'status': '503'}, ''),
481 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800482 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400483 ])
484
485 download = MediaIoBaseDownload(
486 fd=self.fd, request=self.request, chunksize=3)
487
488 self.assertEqual(self.fd, download._fd)
489 self.assertEqual(3, download._chunksize)
490 self.assertEqual(0, download._progress)
491 self.assertEqual(None, download._total_size)
492 self.assertEqual(False, download._done)
493 self.assertEqual(self.request.uri, download._uri)
494
495 # Set time.sleep and random.random stubs.
496 sleeptimes = []
497 download._sleep = lambda x: sleeptimes.append(x)
498 download._rand = lambda: 10
499
500 status, done = download.next_chunk(num_retries=3)
501
502 # Check for exponential backoff using the rand function above.
503 self.assertEqual([20, 40, 80], sleeptimes)
504
Pat Ferate2b140222015-03-03 18:05:11 -0800505 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400506 self.assertEqual(False, done)
507 self.assertEqual(3, download._progress)
508 self.assertEqual(5, download._total_size)
509 self.assertEqual(3, status.resumable_progress)
510
511 # Reset time.sleep stub.
512 del sleeptimes[0:len(sleeptimes)]
513
514 status, done = download.next_chunk(num_retries=3)
515
516 # Check for exponential backoff using the rand function above.
517 self.assertEqual([20, 40, 80], sleeptimes)
518
Pat Ferate2b140222015-03-03 18:05:11 -0800519 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400520 self.assertEqual(True, done)
521 self.assertEqual(5, download._progress)
522 self.assertEqual(5, download._total_size)
523
Joe Gregorio66f57522011-11-30 11:00:00 -0500524EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
525Content-Type: application/json
526MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500527Host: www.googleapis.com
528content-length: 2\r\n\r\n{}"""
529
530
531NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
532Content-Type: application/json
533MIME-Version: 1.0
534Host: www.googleapis.com
535content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500536
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400537NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
538Content-Type: application/json
539MIME-Version: 1.0
540Host: www.googleapis.com\r\n\r\n"""
541
Joe Gregorio66f57522011-11-30 11:00:00 -0500542
543RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400544Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500545Content-Length: 14
546ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
547
548
INADA Naoki09157612015-03-25 01:51:03 +0900549BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500550Content-Type: application/http
551Content-Transfer-Encoding: binary
552Content-ID: <randomness+1>
553
554HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400555Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500556Content-Length: 14
557ETag: "etag/pony"\r\n\r\n{"foo": 42}
558
559--batch_foobarbaz
560Content-Type: application/http
561Content-Transfer-Encoding: binary
562Content-ID: <randomness+2>
563
564HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400565Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500566Content-Length: 14
567ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
568--batch_foobarbaz--"""
569
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500570
INADA Naoki09157612015-03-25 01:51:03 +0900571BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400572Content-Type: application/http
573Content-Transfer-Encoding: binary
574Content-ID: <randomness+1>
575
576HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400577Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400578Content-Length: 14
579ETag: "etag/pony"\r\n\r\n{"foo": 42}
580
581--batch_foobarbaz
582Content-Type: application/http
583Content-Transfer-Encoding: binary
584Content-ID: <randomness+2>
585
586HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400587Content-Type: application/json
588Content-Length: 245
589ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400590 "error": {
591 "errors": [
592 {
593 "domain": "usageLimits",
594 "reason": "accessNotConfigured",
595 "message": "Access Not Configured",
596 "debugInfo": "QuotaState: BLOCKED"
597 }
598 ],
599 "code": 403,
600 "message": "Access Not Configured"
601 }
602}
603
604--batch_foobarbaz--"""
605
606
INADA Naoki09157612015-03-25 01:51:03 +0900607BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500608Content-Type: application/http
609Content-Transfer-Encoding: binary
610Content-ID: <randomness+1>
611
Joe Gregorioc752e332012-07-11 14:43:52 -0400612HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400613Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500614Content-Length: 14
615ETag: "etag/pony"\r\n\r\n{"error": {"message":
616 "Authorizaton failed."}}
617
618--batch_foobarbaz
619Content-Type: application/http
620Content-Transfer-Encoding: binary
621Content-ID: <randomness+2>
622
623HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400624Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500625Content-Length: 14
626ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
627--batch_foobarbaz--"""
628
629
INADA Naoki09157612015-03-25 01:51:03 +0900630BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500631Content-Type: application/http
632Content-Transfer-Encoding: binary
633Content-ID: <randomness+1>
634
635HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400636Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500637Content-Length: 14
638ETag: "etag/pony"\r\n\r\n{"foo": 42}
639--batch_foobarbaz--"""
640
eesheeshc6425a02016-02-12 15:07:06 +0000641
642USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
643 "error": {
644 "errors": [
645 {
646 "domain": "usageLimits",
647 "reason": "userRateLimitExceeded",
648 "message": "User Rate Limit Exceeded"
649 }
650 ],
651 "code": 403,
652 "message": "User Rate Limit Exceeded"
653 }
654}"""
655
656
657RATE_LIMIT_EXCEEDED_RESPONSE = """{
658 "error": {
659 "errors": [
660 {
661 "domain": "usageLimits",
662 "reason": "rateLimitExceeded",
663 "message": "Rate Limit Exceeded"
664 }
665 ],
666 "code": 403,
667 "message": "Rate Limit Exceeded"
668 }
669}"""
670
671
672NOT_CONFIGURED_RESPONSE = """{
673 "error": {
674 "errors": [
675 {
676 "domain": "usageLimits",
677 "reason": "accessNotConfigured",
678 "message": "Access Not Configured"
679 }
680 ],
681 "code": 403,
682 "message": "Access Not Configured"
683 }
684}"""
685
Joe Gregorio654f4a22012-02-09 14:15:44 -0500686class Callbacks(object):
687 def __init__(self):
688 self.responses = {}
689 self.exceptions = {}
690
691 def f(self, request_id, response, exception):
692 self.responses[request_id] = response
693 self.exceptions[request_id] = exception
694
695
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500696class TestHttpRequest(unittest.TestCase):
697 def test_unicode(self):
698 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
699 model = JsonModel()
700 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
701 method = u'POST'
702 request = HttpRequest(
703 http,
704 model.response,
705 uri,
706 method=method,
707 body=u'{}',
708 headers={'content-type': 'application/json'})
709 request.execute()
710 self.assertEqual(uri, http.uri)
711 self.assertEqual(str, type(http.uri))
712 self.assertEqual(method, http.method)
713 self.assertEqual(str, type(http.method))
714
eesheeshc6425a02016-02-12 15:07:06 +0000715 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100716 model = JsonModel()
717 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000718 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
719 model.response,
720 u'https://www.example.com/json_api_endpoint')
721 request._sleep = lambda _x: 0 # do nothing
722 request._rand = lambda: 10
723 with self.assertRaises(socket.error):
724 response = request.execute(num_retries=3)
725
726
727 def test_retry_connection_errors_non_resumable(self):
728 model = JsonModel()
729 request = HttpRequest(
730 HttpMockWithErrors(3, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100731 model.response,
732 u'https://www.example.com/json_api_endpoint')
733 request._sleep = lambda _x: 0 # do nothing
734 request._rand = lambda: 10
735 response = request.execute(num_retries=3)
736 self.assertEqual({u'foo': u'bar'}, response)
737
eesheeshc6425a02016-02-12 15:07:06 +0000738 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100739 with open(datafile('small.png'), 'rb') as small_png_file:
740 small_png_fd = BytesIO(small_png_file.read())
741 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
742 chunksize=500, resumable=True)
743 model = JsonModel()
744
745 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000746 HttpMockWithErrors(
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100747 3, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
748 model.response,
749 u'https://www.example.com/file_upload',
750 method='POST',
751 resumable=upload)
752 request._sleep = lambda _x: 0 # do nothing
753 request._rand = lambda: 10
754 response = request.execute(num_retries=3)
755 self.assertEqual({u'foo': u'bar'}, response)
756
Joe Gregorio9086bd32013-06-14 16:32:05 -0400757 def test_retry(self):
758 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000759 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
760 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
761 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
762 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400763 resp_seq.append(({'status': '200'}, '{}'))
764
765 http = HttpMockSequence(resp_seq)
766 model = JsonModel()
767 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
768 method = u'POST'
769 request = HttpRequest(
770 http,
771 model.response,
772 uri,
773 method=method,
774 body=u'{}',
775 headers={'content-type': 'application/json'})
776
777 sleeptimes = []
778 request._sleep = lambda x: sleeptimes.append(x)
779 request._rand = lambda: 10
780
781 request.execute(num_retries=num_retries)
782
783 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900784 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400785 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
786
eesheeshc6425a02016-02-12 15:07:06 +0000787 def test_no_retry_succeeds(self):
788 num_retries = 5
789 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
790
791 http = HttpMockSequence(resp_seq)
792 model = JsonModel()
793 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
794 method = u'POST'
795 request = HttpRequest(
796 http,
797 model.response,
798 uri,
799 method=method,
800 body=u'{}',
801 headers={'content-type': 'application/json'})
802
803 sleeptimes = []
804 request._sleep = lambda x: sleeptimes.append(x)
805 request._rand = lambda: 10
806
807 request.execute(num_retries=num_retries)
808
809 self.assertEqual(0, len(sleeptimes))
810
Joe Gregorio9086bd32013-06-14 16:32:05 -0400811 def test_no_retry_fails_fast(self):
812 http = HttpMockSequence([
813 ({'status': '500'}, ''),
814 ({'status': '200'}, '{}')
815 ])
816 model = JsonModel()
817 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
818 method = u'POST'
819 request = HttpRequest(
820 http,
821 model.response,
822 uri,
823 method=method,
824 body=u'{}',
825 headers={'content-type': 'application/json'})
826
827 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000828 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400829
eesheeshc6425a02016-02-12 15:07:06 +0000830 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400831 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000832 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400833
eesheeshc6425a02016-02-12 15:07:06 +0000834 def test_no_retry_403_not_configured_fails_fast(self):
835 http = HttpMockSequence([
836 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
837 ({'status': '200'}, '{}')
838 ])
839 model = JsonModel()
840 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
841 method = u'POST'
842 request = HttpRequest(
843 http,
844 model.response,
845 uri,
846 method=method,
847 body=u'{}',
848 headers={'content-type': 'application/json'})
849
850 request._rand = lambda: 1.0
851 request._sleep = mock.MagicMock()
852
853 with self.assertRaises(HttpError):
854 request.execute()
855 request._sleep.assert_not_called()
856
857 def test_no_retry_403_fails_fast(self):
858 http = HttpMockSequence([
859 ({'status': '403'}, ''),
860 ({'status': '200'}, '{}')
861 ])
862 model = JsonModel()
863 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
864 method = u'POST'
865 request = HttpRequest(
866 http,
867 model.response,
868 uri,
869 method=method,
870 body=u'{}',
871 headers={'content-type': 'application/json'})
872
873 request._rand = lambda: 1.0
874 request._sleep = mock.MagicMock()
875
876 with self.assertRaises(HttpError):
877 request.execute()
878 request._sleep.assert_not_called()
879
880 def test_no_retry_401_fails_fast(self):
881 http = HttpMockSequence([
882 ({'status': '401'}, ''),
883 ({'status': '200'}, '{}')
884 ])
885 model = JsonModel()
886 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
887 method = u'POST'
888 request = HttpRequest(
889 http,
890 model.response,
891 uri,
892 method=method,
893 body=u'{}',
894 headers={'content-type': 'application/json'})
895
896 request._rand = lambda: 1.0
897 request._sleep = mock.MagicMock()
898
899 with self.assertRaises(HttpError):
900 request.execute()
901 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500902
Joe Gregorio66f57522011-11-30 11:00:00 -0500903class TestBatch(unittest.TestCase):
904
905 def setUp(self):
906 model = JsonModel()
907 self.request1 = HttpRequest(
908 None,
909 model.response,
910 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
911 method='POST',
912 body='{}',
913 headers={'content-type': 'application/json'})
914
915 self.request2 = HttpRequest(
916 None,
917 model.response,
918 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500919 method='GET',
920 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500921 headers={'content-type': 'application/json'})
922
923
924 def test_id_to_from_content_id_header(self):
925 batch = BatchHttpRequest()
926 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
927
928 def test_invalid_content_id_header(self):
929 batch = BatchHttpRequest()
930 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
931 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
932 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
933
934 def test_serialize_request(self):
935 batch = BatchHttpRequest()
936 request = HttpRequest(
937 None,
938 None,
939 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
940 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800941 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -0500942 headers={'content-type': 'application/json'},
943 methodId=None,
944 resumable=None)
945 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500946 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500947
Joe Gregoriodd813822012-01-25 10:32:47 -0500948 def test_serialize_request_media_body(self):
949 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -0800950 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -0500951 body = f.read()
952 f.close()
953
954 request = HttpRequest(
955 None,
956 None,
957 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
958 method='POST',
959 body=body,
960 headers={'content-type': 'application/json'},
961 methodId=None,
962 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500963 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -0500964 s = batch._serialize_request(request).splitlines()
965
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500966 def test_serialize_request_no_body(self):
967 batch = BatchHttpRequest()
968 request = HttpRequest(
969 None,
970 None,
971 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
972 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800973 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500974 headers={'content-type': 'application/json'},
975 methodId=None,
976 resumable=None)
977 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500978 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500979
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400980 def test_serialize_get_request_no_body(self):
981 batch = BatchHttpRequest()
982 request = HttpRequest(
983 None,
984 None,
985 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
986 method='GET',
987 body=None,
988 headers={'content-type': 'application/json'},
989 methodId=None,
990 resumable=None)
991 s = batch._serialize_request(request).splitlines()
992 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
993
Joe Gregorio66f57522011-11-30 11:00:00 -0500994 def test_deserialize_response(self):
995 batch = BatchHttpRequest()
996 resp, content = batch._deserialize_response(RESPONSE)
997
Joe Gregorio654f4a22012-02-09 14:15:44 -0500998 self.assertEqual(200, resp.status)
999 self.assertEqual('OK', resp.reason)
1000 self.assertEqual(11, resp.version)
1001 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001002
1003 def test_new_id(self):
1004 batch = BatchHttpRequest()
1005
1006 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001007 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001008
1009 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001010 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001011
1012 batch.add(self.request1, request_id='3')
1013
1014 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001015 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001016
1017 def test_add(self):
1018 batch = BatchHttpRequest()
1019 batch.add(self.request1, request_id='1')
1020 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1021
1022 def test_add_fail_for_resumable(self):
1023 batch = BatchHttpRequest()
1024
1025 upload = MediaFileUpload(
1026 datafile('small.png'), chunksize=500, resumable=True)
1027 self.request1.resumable = upload
1028 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
1029
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001030 def test_execute_empty_batch_no_http(self):
1031 batch = BatchHttpRequest()
1032 ret = batch.execute()
1033 self.assertEqual(None, ret)
1034
Joe Gregorio66f57522011-11-30 11:00:00 -05001035 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001036 batch = BatchHttpRequest()
1037 callbacks = Callbacks()
1038
1039 batch.add(self.request1, callback=callbacks.f)
1040 batch.add(self.request2, callback=callbacks.f)
1041 http = HttpMockSequence([
1042 ({'status': '200',
1043 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1044 BATCH_RESPONSE),
1045 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001046 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001047 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1048 self.assertEqual(None, callbacks.exceptions['1'])
1049 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1050 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001051
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001052 def test_execute_request_body(self):
1053 batch = BatchHttpRequest()
1054
1055 batch.add(self.request1)
1056 batch.add(self.request2)
1057 http = HttpMockSequence([
1058 ({'status': '200',
1059 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1060 'echo_request_body'),
1061 ])
1062 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001063 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001064 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001065 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001066 boundary, _ = e.content.split(None, 1)
1067 self.assertEqual('--', boundary[:2])
1068 parts = e.content.split(boundary)
1069 self.assertEqual(4, len(parts))
1070 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001071 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001072 header = parts[1].splitlines()[1]
1073 self.assertEqual('Content-Type: application/http', header)
1074
Gabriel Garcia23174be2016-05-25 17:28:07 +02001075 def test_execute_initial_refresh_oauth2(self):
1076 batch = BatchHttpRequest()
1077 callbacks = Callbacks()
1078 cred = MockCredentials('Foo')
1079
1080 # Pretend this is a OAuth2Credentials object
1081 cred.access_token = None
1082
1083 http = HttpMockSequence([
1084 ({'status': '200',
1085 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1086 BATCH_SINGLE_RESPONSE),
1087 ])
1088
1089 cred.authorize(http)
1090
1091 batch.add(self.request1, callback=callbacks.f)
1092 batch.execute(http=http)
1093
1094 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1095 self.assertIsNone(callbacks.exceptions['1'])
1096
1097 self.assertEqual(1, cred._refreshed)
1098
1099 self.assertEqual(1, cred._authorized)
1100
1101 self.assertEqual(1, cred._applied)
1102
Joe Gregorio654f4a22012-02-09 14:15:44 -05001103 def test_execute_refresh_and_retry_on_401(self):
1104 batch = BatchHttpRequest()
1105 callbacks = Callbacks()
1106 cred_1 = MockCredentials('Foo')
1107 cred_2 = MockCredentials('Bar')
1108
1109 http = HttpMockSequence([
1110 ({'status': '200',
1111 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1112 BATCH_RESPONSE_WITH_401),
1113 ({'status': '200',
1114 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1115 BATCH_SINGLE_RESPONSE),
1116 ])
1117
1118 creds_http_1 = HttpMockSequence([])
1119 cred_1.authorize(creds_http_1)
1120
1121 creds_http_2 = HttpMockSequence([])
1122 cred_2.authorize(creds_http_2)
1123
1124 self.request1.http = creds_http_1
1125 self.request2.http = creds_http_2
1126
1127 batch.add(self.request1, callback=callbacks.f)
1128 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001129 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001130
1131 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1132 self.assertEqual(None, callbacks.exceptions['1'])
1133 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1134 self.assertEqual(None, callbacks.exceptions['2'])
1135
1136 self.assertEqual(1, cred_1._refreshed)
1137 self.assertEqual(0, cred_2._refreshed)
1138
1139 self.assertEqual(1, cred_1._authorized)
1140 self.assertEqual(1, cred_2._authorized)
1141
1142 self.assertEqual(1, cred_2._applied)
1143 self.assertEqual(2, cred_1._applied)
1144
1145 def test_http_errors_passed_to_callback(self):
1146 batch = BatchHttpRequest()
1147 callbacks = Callbacks()
1148 cred_1 = MockCredentials('Foo')
1149 cred_2 = MockCredentials('Bar')
1150
1151 http = HttpMockSequence([
1152 ({'status': '200',
1153 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1154 BATCH_RESPONSE_WITH_401),
1155 ({'status': '200',
1156 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1157 BATCH_RESPONSE_WITH_401),
1158 ])
1159
1160 creds_http_1 = HttpMockSequence([])
1161 cred_1.authorize(creds_http_1)
1162
1163 creds_http_2 = HttpMockSequence([])
1164 cred_2.authorize(creds_http_2)
1165
1166 self.request1.http = creds_http_1
1167 self.request2.http = creds_http_2
1168
1169 batch.add(self.request1, callback=callbacks.f)
1170 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001171 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001172
1173 self.assertEqual(None, callbacks.responses['1'])
1174 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001175 self.assertEqual(
1176 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001177 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1178 self.assertEqual(None, callbacks.exceptions['2'])
1179
Joe Gregorio66f57522011-11-30 11:00:00 -05001180 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001181 callbacks = Callbacks()
1182 batch = BatchHttpRequest(callback=callbacks.f)
1183
1184 batch.add(self.request1)
1185 batch.add(self.request2)
1186 http = HttpMockSequence([
1187 ({'status': '200',
1188 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1189 BATCH_RESPONSE),
1190 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001191 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001192 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1193 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001194
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001195 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001196 callbacks = Callbacks()
1197 batch = BatchHttpRequest(callback=callbacks.f)
1198
1199 batch.add(self.request1)
1200 batch.add(self.request2)
1201 http = HttpMockSequence([
1202 ({'status': '200',
1203 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1204 BATCH_ERROR_RESPONSE),
1205 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001206 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001207 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1208 expected = ('<HttpError 403 when requesting '
1209 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1210 '"Access Not Configured">')
1211 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001212
Joe Gregorio5c120db2012-08-23 09:13:55 -04001213
Joe Gregorioba5c7902012-08-03 12:48:16 -04001214class TestRequestUriTooLong(unittest.TestCase):
1215
1216 def test_turn_get_into_post(self):
1217
1218 def _postproc(resp, content):
1219 return content
1220
1221 http = HttpMockSequence([
1222 ({'status': '200'},
1223 'echo_request_body'),
1224 ({'status': '200'},
1225 'echo_request_headers'),
1226 ])
1227
1228 # Send a long query parameter.
1229 query = {
1230 'q': 'a' * MAX_URI_LENGTH + '?&'
1231 }
1232 req = HttpRequest(
1233 http,
1234 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001235 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001236 method='GET',
1237 body=None,
1238 headers={},
1239 methodId='foo',
1240 resumable=None)
1241
1242 # Query parameters should be sent in the body.
1243 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001244 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001245
1246 # Extra headers should be set.
1247 response = req.execute()
1248 self.assertEqual('GET', response['x-http-method-override'])
1249 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1250 self.assertEqual(
1251 'application/x-www-form-urlencoded', response['content-type'])
1252
Joe Gregorio5c120db2012-08-23 09:13:55 -04001253
1254class TestStreamSlice(unittest.TestCase):
1255 """Test _StreamSlice."""
1256
1257 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001258 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001259
1260 def test_read(self):
1261 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001262 self.assertEqual(b'', s.read(0))
1263 self.assertEqual(b'0', s.read(1))
1264 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001265
1266 def test_read_too_much(self):
1267 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001268 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001269
1270 def test_read_all(self):
1271 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001272 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001273
Ali Afshar164f37e2013-01-07 14:05:45 -08001274
1275class TestResponseCallback(unittest.TestCase):
1276 """Test adding callbacks to responses."""
1277
1278 def test_ensure_response_callback(self):
1279 m = JsonModel()
1280 request = HttpRequest(
1281 None,
1282 m.response,
1283 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1284 method='POST',
1285 body='{}',
1286 headers={'content-type': 'application/json'})
1287 h = HttpMockSequence([ ({'status': 200}, '{}')])
1288 responses = []
1289 def _on_response(resp, responses=responses):
1290 responses.append(resp)
1291 request.add_response_callback(_on_response)
1292 request.execute(http=h)
1293 self.assertEqual(1, len(responses))
1294
1295
Craig Gurnik8e55b762015-01-20 15:00:10 -05001296class TestHttpMock(unittest.TestCase):
1297 def test_default_response_headers(self):
1298 http = HttpMock(datafile('zoo.json'))
1299 resp, content = http.request("http://example.com")
1300 self.assertEqual(resp.status, 200)
1301
Alan Briolat26b01002015-08-14 00:13:57 +01001302 def test_error_response(self):
1303 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1304 model = JsonModel()
1305 request = HttpRequest(
1306 http,
1307 model.response,
1308 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1309 method='GET',
1310 headers={})
1311 self.assertRaises(HttpError, request.execute)
1312
Craig Gurnik8e55b762015-01-20 15:00:10 -05001313
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001314if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001315 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001316 unittest.main()