blob: 6c2748bf23c09fb28409150ec1fb9400dc718516 [file] [log] [blame]
Craig Citro15744b12015-03-02 13:34:32 -08001#!/usr/bin/env python
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05002#
Craig Citro751b7fb2014-09-23 11:20:38 -07003# Copyright 2014 Google Inc. All Rights Reserved.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Http tests
18
John Asmuth864311d2014-04-24 15:46:08 -040019Unit tests for the googleapiclient.http.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050020"""
INADA Naokid898a372015-03-04 03:52:46 +090021from __future__ import absolute_import
22from six.moves import range
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050023
24__author__ = 'jcgregorio@google.com (Joe Gregorio)'
25
Pat Feratec6050872015-03-03 18:24:59 -080026from six import PY3
Pat Ferateed9affd2015-03-03 16:03:15 -080027from six import BytesIO, StringIO
28from io import FileIO
Pat Ferated5b61bd2015-03-03 16:04:11 -080029from six.moves.urllib.parse import urlencode
Pat Ferateed9affd2015-03-03 16:03:15 -080030
Joe Gregorio7cbceab2011-06-27 10:46:54 -040031# Do not remove the httplib2 import
Chris McDonough0dc81bf2018-07-19 11:19:58 -040032import json
Joe Gregorio7cbceab2011-06-27 10:46:54 -040033import httplib2
Joe Gregorio9086bd32013-06-14 16:32:05 -040034import logging
eesheeshc6425a02016-02-12 15:07:06 +000035import mock
Joe Gregoriod0bd3882011-11-22 09:49:47 -050036import os
Pat Ferate497a90f2015-03-09 09:52:54 -070037import unittest2 as unittest
Joe Gregorio9086bd32013-06-14 16:32:05 -040038import random
eesheeshc6425a02016-02-12 15:07:06 +000039import socket
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +010040import ssl
Joe Gregorio9086bd32013-06-14 16:32:05 -040041import time
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050042
John Asmuth864311d2014-04-24 15:46:08 -040043from googleapiclient.discovery import build
44from googleapiclient.errors import BatchError
45from googleapiclient.errors import HttpError
46from googleapiclient.errors import InvalidChunkSizeError
Igor Maravić22435292017-01-19 22:28:22 +010047from googleapiclient.http import build_http
John Asmuth864311d2014-04-24 15:46:08 -040048from googleapiclient.http import BatchHttpRequest
49from googleapiclient.http import HttpMock
50from googleapiclient.http import HttpMockSequence
51from googleapiclient.http import HttpRequest
52from googleapiclient.http import MAX_URI_LENGTH
53from googleapiclient.http import MediaFileUpload
54from googleapiclient.http import MediaInMemoryUpload
55from googleapiclient.http import MediaIoBaseDownload
56from googleapiclient.http import MediaIoBaseUpload
57from googleapiclient.http import MediaUpload
58from googleapiclient.http import _StreamSlice
59from googleapiclient.http import set_user_agent
60from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050061from oauth2client.client import Credentials
62
63
64class MockCredentials(Credentials):
65 """Mock class for all Credentials objects."""
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070066 def __init__(self, bearer_token, expired=False):
Joe Gregorio654f4a22012-02-09 14:15:44 -050067 super(MockCredentials, self).__init__()
68 self._authorized = 0
69 self._refreshed = 0
70 self._applied = 0
71 self._bearer_token = bearer_token
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070072 self._access_token_expired = expired
73
74 @property
Jon Wayne Parrott20e61352018-01-18 09:16:37 -080075 def access_token(self):
76 return self._bearer_token
77
78 @property
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070079 def access_token_expired(self):
80 return self._access_token_expired
Joe Gregorio654f4a22012-02-09 14:15:44 -050081
82 def authorize(self, http):
83 self._authorized += 1
84
85 request_orig = http.request
86
87 # The closure that will replace 'httplib2.Http.request'.
88 def new_request(uri, method='GET', body=None, headers=None,
89 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
90 connection_type=None):
91 # Modify the request headers to add the appropriate
92 # Authorization header.
93 if headers is None:
94 headers = {}
95 self.apply(headers)
96
97 resp, content = request_orig(uri, method, body, headers,
98 redirections, connection_type)
99
100 return resp, content
101
102 # Replace the request method with our own closure.
103 http.request = new_request
104
105 # Set credentials as a property of the request method.
106 setattr(http.request, 'credentials', self)
107
108 return http
109
110 def refresh(self, http):
111 self._refreshed += 1
112
113 def apply(self, headers):
114 self._applied += 1
115 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500116
117
eesheeshc6425a02016-02-12 15:07:06 +0000118class HttpMockWithErrors(object):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100119 def __init__(self, num_errors, success_json, success_data):
120 self.num_errors = num_errors
121 self.success_json = success_json
122 self.success_data = success_data
123
124 def request(self, *args, **kwargs):
125 if not self.num_errors:
126 return httplib2.Response(self.success_json), self.success_data
127 else:
128 self.num_errors -= 1
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700129 if self.num_errors == 1: # initial == 2
eesheeshc6425a02016-02-12 15:07:06 +0000130 raise ssl.SSLError()
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400131 if self.num_errors == 3: # initial == 4
132 raise httplib2.ServerNotFoundError()
133 else: # initial != 2,4
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200134 if self.num_errors == 2:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700135 # first try a broken pipe error (#218)
136 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200137 ex.errno = socket.errno.EPIPE
138 else:
139 # Initialize the timeout error code to the platform's error code.
140 try:
141 # For Windows:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700142 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200143 ex.errno = socket.errno.WSAETIMEDOUT
144 except AttributeError:
145 # For Linux/Mac:
Alexander Mohrfff3ae52018-04-27 13:39:53 -0700146 if PY3:
147 ex = socket.timeout()
148 else:
149 ex = socket.error()
150 ex.errno = socket.errno.ETIMEDOUT
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200151 # Now raise the correct error.
eesheeshc6425a02016-02-12 15:07:06 +0000152 raise ex
153
154
155class HttpMockWithNonRetriableErrors(object):
156 def __init__(self, num_errors, success_json, success_data):
157 self.num_errors = num_errors
158 self.success_json = success_json
159 self.success_data = success_data
160
161 def request(self, *args, **kwargs):
162 if not self.num_errors:
163 return httplib2.Response(self.success_json), self.success_data
164 else:
165 self.num_errors -= 1
166 ex = socket.error()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200167 # set errno to a non-retriable value
eesheeshc6425a02016-02-12 15:07:06 +0000168 try:
169 # For Windows:
170 ex.errno = socket.errno.WSAECONNREFUSED
171 except AttributeError:
172 # For Linux/Mac:
173 ex.errno = socket.errno.ECONNREFUSED
174 # Now raise the correct timeout error.
175 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100176
177
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500178DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
179
180
181def datafile(filename):
182 return os.path.join(DATA_DIR, filename)
183
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100184def _postproc_none(*kwargs):
185 pass
186
187
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500188class TestUserAgent(unittest.TestCase):
189
190 def test_set_user_agent(self):
191 http = HttpMockSequence([
192 ({'status': '200'}, 'echo_request_headers'),
193 ])
194
195 http = set_user_agent(http, "my_app/5.5")
196 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500197 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500198
199 def test_set_user_agent_nested(self):
200 http = HttpMockSequence([
201 ({'status': '200'}, 'echo_request_headers'),
202 ])
203
204 http = set_user_agent(http, "my_app/5.5")
205 http = set_user_agent(http, "my_library/0.1")
206 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500207 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500208
Joe Gregorio910b9b12012-06-12 09:36:30 -0400209
210class TestMediaUpload(unittest.TestCase):
211
Nam T. Nguyendc136312015-12-01 10:18:56 -0800212 def test_media_file_upload_mimetype_detection(self):
213 upload = MediaFileUpload(datafile('small.png'))
214 self.assertEqual('image/png', upload.mimetype())
215
216 upload = MediaFileUpload(datafile('empty'))
217 self.assertEqual('application/octet-stream', upload.mimetype())
218
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500219 def test_media_file_upload_to_from_json(self):
220 upload = MediaFileUpload(
221 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500222 self.assertEqual('image/png', upload.mimetype())
223 self.assertEqual(190, upload.size())
224 self.assertEqual(True, upload.resumable())
225 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800226 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500227
228 json = upload.to_json()
229 new_upload = MediaUpload.new_from_json(json)
230
Joe Gregorio654f4a22012-02-09 14:15:44 -0500231 self.assertEqual('image/png', new_upload.mimetype())
232 self.assertEqual(190, new_upload.size())
233 self.assertEqual(True, new_upload.resumable())
234 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800235 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500236
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400237 def test_media_file_upload_raises_on_invalid_chunksize(self):
238 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
239 datafile('small.png'), mimetype='image/png', chunksize=-2,
240 resumable=True)
241
Ali Afshar1cb6b672012-03-12 08:46:14 -0400242 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800243 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400244 resumable=True)
245 self.assertEqual('text/plain', media.mimetype())
246 self.assertEqual(10, media.chunksize())
247 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800248 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400249 self.assertEqual(6, media.size())
250
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500251 def test_http_request_to_from_json(self):
Igor Maravić22435292017-01-19 22:28:22 +0100252 http = build_http()
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500253 media_upload = MediaFileUpload(
254 datafile('small.png'), chunksize=500, resumable=True)
255 req = HttpRequest(
256 http,
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100257 _postproc_none,
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500258 'http://example.com',
259 method='POST',
260 body='{}',
261 headers={'content-type': 'multipart/related; boundary="---flubber"'},
262 methodId='foo',
263 resumable=media_upload)
264
265 json = req.to_json()
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100266 new_req = HttpRequest.from_json(json, http, _postproc_none)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500267
Joe Gregorio654f4a22012-02-09 14:15:44 -0500268 self.assertEqual({'content-type':
269 'multipart/related; boundary="---flubber"'},
270 new_req.headers)
271 self.assertEqual('http://example.com', new_req.uri)
272 self.assertEqual('{}', new_req.body)
273 self.assertEqual(http, new_req.http)
274 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500275
Joe Gregorio9086bd32013-06-14 16:32:05 -0400276 self.assertEqual(random.random, new_req._rand)
277 self.assertEqual(time.sleep, new_req._sleep)
278
Joe Gregorio910b9b12012-06-12 09:36:30 -0400279
280class TestMediaIoBaseUpload(unittest.TestCase):
281
282 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800283 fd = FileIO(datafile('small.png'), 'r')
284 upload = MediaIoBaseUpload(
285 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
286 self.assertEqual('image/png', upload.mimetype())
287 self.assertEqual(190, upload.size())
288 self.assertEqual(True, upload.resumable())
289 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800290 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400291
292 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800293 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400294 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400295 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400296 self.assertEqual('image/png', upload.mimetype())
297 self.assertEqual(190, upload.size())
298 self.assertEqual(True, upload.resumable())
299 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800300 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400301 f.close()
302
303 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800304 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400305 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400306
307 try:
308 json = upload.to_json()
309 self.fail('MediaIoBaseUpload should not be serializable.')
310 except NotImplementedError:
311 pass
312
Pat Feratec6050872015-03-03 18:24:59 -0800313 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400314 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800315 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800316 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400317 f.close()
318
319 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400320 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400321 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400322 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400323 self.assertEqual(True, upload.resumable())
324 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800325 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400326 f.close()
327
328 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800329 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800330 fd = BytesIO(f.read())
331 upload = MediaIoBaseUpload(
332 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
333 self.assertEqual('image/png', upload.mimetype())
334 self.assertEqual(190, upload.size())
335 self.assertEqual(True, upload.resumable())
336 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800337 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400338
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400339 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800340 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800341 fd = BytesIO(f.read())
342 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
343 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400344
345 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800346 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800347 upload = MediaIoBaseUpload(
348 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
349 self.assertEqual(True, upload.has_stream())
350 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400351
Joe Gregorio9086bd32013-06-14 16:32:05 -0400352 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800353 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800354 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400355 upload = MediaIoBaseUpload(
356 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
357
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500358 # Simulate errors for both the request that creates the resumable upload
359 # and the upload itself.
Joe Gregorio9086bd32013-06-14 16:32:05 -0400360 http = HttpMockSequence([
361 ({'status': '500'}, ''),
362 ({'status': '500'}, ''),
363 ({'status': '503'}, ''),
364 ({'status': '200', 'location': 'location'}, ''),
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500365 ({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
366 ({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE),
367 ({'status': '429'}, ''),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400368 ({'status': '200'}, '{}'),
369 ])
370
371 model = JsonModel()
372 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
373 method = u'POST'
374 request = HttpRequest(
375 http,
376 model.response,
377 uri,
378 method=method,
379 headers={},
380 resumable=upload)
381
382 sleeptimes = []
383 request._sleep = lambda x: sleeptimes.append(x)
384 request._rand = lambda: 10
385
386 request.execute(num_retries=3)
387 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
388
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500389 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
390 fd = BytesIO(b"i am png")
391 upload = MediaIoBaseUpload(
392 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
393
394 http = HttpMockSequence([
395 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
396 ({'status': '200'}, '{}')
397 ])
398
399 model = JsonModel()
400 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
401 method = u'POST'
402 request = HttpRequest(
403 http,
404 model.response,
405 uri,
406 method=method,
407 headers={},
408 resumable=upload)
409
410 request._rand = lambda: 1.0
411 request._sleep = mock.MagicMock()
412
413 with self.assertRaises(HttpError):
414 request.execute(num_retries=3)
415 request._sleep.assert_not_called()
416
Joe Gregorio910b9b12012-06-12 09:36:30 -0400417
Joe Gregorio708388c2012-06-15 13:43:04 -0400418class TestMediaIoBaseDownload(unittest.TestCase):
419
420 def setUp(self):
421 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400422 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400423 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800424 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400425
426 def test_media_io_base_download(self):
427 self.request.http = HttpMockSequence([
428 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800429 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400430 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800431 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400432 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400433 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400434
435 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400436 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400437
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400438 self.assertEqual(self.fd, download._fd)
439 self.assertEqual(3, download._chunksize)
440 self.assertEqual(0, download._progress)
441 self.assertEqual(None, download._total_size)
442 self.assertEqual(False, download._done)
443 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400444
445 status, done = download.next_chunk()
446
Pat Ferate2b140222015-03-03 18:05:11 -0800447 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400448 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400449 self.assertEqual(3, download._progress)
450 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400451 self.assertEqual(3, status.resumable_progress)
452
453 status, done = download.next_chunk()
454
Pat Ferate2b140222015-03-03 18:05:11 -0800455 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400456 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400457 self.assertEqual(5, download._progress)
458 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400459
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400460 def test_media_io_base_download_custom_request_headers(self):
461 self.request.http = HttpMockSequence([
462 ({'status': '200',
463 'content-range': '0-2/5'}, 'echo_request_headers_as_json'),
464 ({'status': '200',
465 'content-range': '3-4/5'}, 'echo_request_headers_as_json'),
466 ])
467 self.assertEqual(True, self.request.http.follow_redirects)
468
469 self.request.headers['Cache-Control'] = 'no-store'
470
471 download = MediaIoBaseDownload(
472 fd=self.fd, request=self.request, chunksize=3)
473
474 self.assertEqual(download._headers, {'Cache-Control':'no-store'})
475
476 status, done = download.next_chunk()
477
478 result = self.fd.getvalue().decode('utf-8')
479
480 # we abuse the internals of the object we're testing, pay no attention
481 # to the actual bytes= values here; we are just asserting that the
482 # header we added to the original request is sent up to the server
483 # on each call to next_chunk
484
485 self.assertEqual(json.loads(result),
486 {"Cache-Control": "no-store", "range": "bytes=0-3"})
487
488 download._fd = self.fd = BytesIO()
489 status, done = download.next_chunk()
490
491 result = self.fd.getvalue().decode('utf-8')
492 self.assertEqual(json.loads(result),
493 {"Cache-Control": "no-store", "range": "bytes=51-54"})
494
Joe Gregorio708388c2012-06-15 13:43:04 -0400495 def test_media_io_base_download_handle_redirects(self):
496 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400497 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800498 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400499 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800500 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400501 ])
502
503 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400504 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400505
506 status, done = download.next_chunk()
507
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400508 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400509
510 def test_media_io_base_download_handle_4xx(self):
511 self.request.http = HttpMockSequence([
512 ({'status': '400'}, ''),
513 ])
514
515 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400516 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400517
518 try:
519 status, done = download.next_chunk()
520 self.fail('Should raise an exception')
521 except HttpError:
522 pass
523
524 # Even after raising an exception we can pick up where we left off.
525 self.request.http = HttpMockSequence([
526 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800527 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400528 ])
529
530 status, done = download.next_chunk()
531
Pat Ferate2b140222015-03-03 18:05:11 -0800532 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400533
eesheeshc6425a02016-02-12 15:07:06 +0000534 def test_media_io_base_download_retries_connection_errors(self):
535 self.request.http = HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400536 4, {'status': '200', 'content-range': '0-2/3'}, b'123')
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100537
538 download = MediaIoBaseDownload(
539 fd=self.fd, request=self.request, chunksize=3)
540 download._sleep = lambda _x: 0 # do nothing
541 download._rand = lambda: 10
542
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400543 status, done = download.next_chunk(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100544
545 self.assertEqual(self.fd.getvalue(), b'123')
546 self.assertEqual(True, done)
547
Joe Gregorio9086bd32013-06-14 16:32:05 -0400548 def test_media_io_base_download_retries_5xx(self):
549 self.request.http = HttpMockSequence([
550 ({'status': '500'}, ''),
551 ({'status': '500'}, ''),
552 ({'status': '500'}, ''),
553 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800554 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400555 ({'status': '503'}, ''),
556 ({'status': '503'}, ''),
557 ({'status': '503'}, ''),
558 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800559 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400560 ])
561
562 download = MediaIoBaseDownload(
563 fd=self.fd, request=self.request, chunksize=3)
564
565 self.assertEqual(self.fd, download._fd)
566 self.assertEqual(3, download._chunksize)
567 self.assertEqual(0, download._progress)
568 self.assertEqual(None, download._total_size)
569 self.assertEqual(False, download._done)
570 self.assertEqual(self.request.uri, download._uri)
571
572 # Set time.sleep and random.random stubs.
573 sleeptimes = []
574 download._sleep = lambda x: sleeptimes.append(x)
575 download._rand = lambda: 10
576
577 status, done = download.next_chunk(num_retries=3)
578
579 # Check for exponential backoff using the rand function above.
580 self.assertEqual([20, 40, 80], sleeptimes)
581
Pat Ferate2b140222015-03-03 18:05:11 -0800582 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400583 self.assertEqual(False, done)
584 self.assertEqual(3, download._progress)
585 self.assertEqual(5, download._total_size)
586 self.assertEqual(3, status.resumable_progress)
587
588 # Reset time.sleep stub.
589 del sleeptimes[0:len(sleeptimes)]
590
591 status, done = download.next_chunk(num_retries=3)
592
593 # Check for exponential backoff using the rand function above.
594 self.assertEqual([20, 40, 80], sleeptimes)
595
Pat Ferate2b140222015-03-03 18:05:11 -0800596 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400597 self.assertEqual(True, done)
598 self.assertEqual(5, download._progress)
599 self.assertEqual(5, download._total_size)
600
andrewnestera4a44cf2017-03-31 16:09:31 +0300601 def test_media_io_base_download_empty_file(self):
602 self.request.http = HttpMockSequence([
603 ({'status': '200',
604 'content-range': '0-0/0'}, b''),
605 ])
606
607 download = MediaIoBaseDownload(
608 fd=self.fd, request=self.request, chunksize=3)
609
610 self.assertEqual(self.fd, download._fd)
611 self.assertEqual(0, download._progress)
612 self.assertEqual(None, download._total_size)
613 self.assertEqual(False, download._done)
614 self.assertEqual(self.request.uri, download._uri)
615
616 status, done = download.next_chunk()
617
618 self.assertEqual(True, done)
619 self.assertEqual(0, download._progress)
620 self.assertEqual(0, download._total_size)
621 self.assertEqual(0, status.progress())
622
Daniel44067782018-01-16 23:17:56 +0100623 def test_media_io_base_download_unknown_media_size(self):
624 self.request.http = HttpMockSequence([
625 ({'status': '200'}, b'123')
626 ])
627
628 download = MediaIoBaseDownload(
629 fd=self.fd, request=self.request, chunksize=3)
630
631 self.assertEqual(self.fd, download._fd)
632 self.assertEqual(0, download._progress)
633 self.assertEqual(None, download._total_size)
634 self.assertEqual(False, download._done)
635 self.assertEqual(self.request.uri, download._uri)
636
637 status, done = download.next_chunk()
638
639 self.assertEqual(self.fd.getvalue(), b'123')
640 self.assertEqual(True, done)
641 self.assertEqual(3, download._progress)
642 self.assertEqual(None, download._total_size)
643 self.assertEqual(0, status.progress())
644
645
Joe Gregorio66f57522011-11-30 11:00:00 -0500646EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
647Content-Type: application/json
648MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500649Host: www.googleapis.com
650content-length: 2\r\n\r\n{}"""
651
652
653NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
654Content-Type: application/json
655MIME-Version: 1.0
656Host: www.googleapis.com
657content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500658
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400659NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
660Content-Type: application/json
661MIME-Version: 1.0
662Host: www.googleapis.com\r\n\r\n"""
663
Joe Gregorio66f57522011-11-30 11:00:00 -0500664
665RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400666Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500667Content-Length: 14
668ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
669
670
INADA Naoki09157612015-03-25 01:51:03 +0900671BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500672Content-Type: application/http
673Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400674Content-ID: <randomness + 1>
Joe Gregorio66f57522011-11-30 11:00:00 -0500675
676HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400677Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500678Content-Length: 14
679ETag: "etag/pony"\r\n\r\n{"foo": 42}
680
681--batch_foobarbaz
682Content-Type: application/http
683Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400684Content-ID: <randomness + 2>
Joe Gregorio66f57522011-11-30 11:00:00 -0500685
686HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400687Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500688Content-Length: 14
689ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
690--batch_foobarbaz--"""
691
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500692
INADA Naoki09157612015-03-25 01:51:03 +0900693BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400694Content-Type: application/http
695Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400696Content-ID: <randomness + 1>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400697
698HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400699Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400700Content-Length: 14
701ETag: "etag/pony"\r\n\r\n{"foo": 42}
702
703--batch_foobarbaz
704Content-Type: application/http
705Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400706Content-ID: <randomness + 2>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400707
708HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400709Content-Type: application/json
710Content-Length: 245
711ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400712 "error": {
713 "errors": [
714 {
715 "domain": "usageLimits",
716 "reason": "accessNotConfigured",
717 "message": "Access Not Configured",
718 "debugInfo": "QuotaState: BLOCKED"
719 }
720 ],
721 "code": 403,
722 "message": "Access Not Configured"
723 }
724}
725
726--batch_foobarbaz--"""
727
728
INADA Naoki09157612015-03-25 01:51:03 +0900729BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500730Content-Type: application/http
731Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400732Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500733
Joe Gregorioc752e332012-07-11 14:43:52 -0400734HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400735Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500736Content-Length: 14
737ETag: "etag/pony"\r\n\r\n{"error": {"message":
738 "Authorizaton failed."}}
739
740--batch_foobarbaz
741Content-Type: application/http
742Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400743Content-ID: <randomness + 2>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500744
745HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400746Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500747Content-Length: 14
748ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
749--batch_foobarbaz--"""
750
751
INADA Naoki09157612015-03-25 01:51:03 +0900752BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500753Content-Type: application/http
754Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400755Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500756
757HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400758Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500759Content-Length: 14
760ETag: "etag/pony"\r\n\r\n{"foo": 42}
761--batch_foobarbaz--"""
762
eesheeshc6425a02016-02-12 15:07:06 +0000763
764USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
765 "error": {
766 "errors": [
767 {
768 "domain": "usageLimits",
769 "reason": "userRateLimitExceeded",
770 "message": "User Rate Limit Exceeded"
771 }
772 ],
773 "code": 403,
774 "message": "User Rate Limit Exceeded"
775 }
776}"""
777
778
779RATE_LIMIT_EXCEEDED_RESPONSE = """{
780 "error": {
781 "errors": [
782 {
783 "domain": "usageLimits",
784 "reason": "rateLimitExceeded",
785 "message": "Rate Limit Exceeded"
786 }
787 ],
788 "code": 403,
789 "message": "Rate Limit Exceeded"
790 }
791}"""
792
793
794NOT_CONFIGURED_RESPONSE = """{
795 "error": {
796 "errors": [
797 {
798 "domain": "usageLimits",
799 "reason": "accessNotConfigured",
800 "message": "Access Not Configured"
801 }
802 ],
803 "code": 403,
804 "message": "Access Not Configured"
805 }
806}"""
807
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800808LIST_NOT_CONFIGURED_RESPONSE = """[
809 "error": {
810 "errors": [
811 {
812 "domain": "usageLimits",
813 "reason": "accessNotConfigured",
814 "message": "Access Not Configured"
815 }
816 ],
817 "code": 403,
818 "message": "Access Not Configured"
819 }
820]"""
821
Joe Gregorio654f4a22012-02-09 14:15:44 -0500822class Callbacks(object):
823 def __init__(self):
824 self.responses = {}
825 self.exceptions = {}
826
827 def f(self, request_id, response, exception):
828 self.responses[request_id] = response
829 self.exceptions[request_id] = exception
830
831
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500832class TestHttpRequest(unittest.TestCase):
833 def test_unicode(self):
834 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
835 model = JsonModel()
836 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
837 method = u'POST'
838 request = HttpRequest(
839 http,
840 model.response,
841 uri,
842 method=method,
843 body=u'{}',
844 headers={'content-type': 'application/json'})
845 request.execute()
846 self.assertEqual(uri, http.uri)
847 self.assertEqual(str, type(http.uri))
848 self.assertEqual(method, http.method)
849 self.assertEqual(str, type(http.method))
850
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100851 def test_empty_content_type(self):
852 """Test for #284"""
853 http = HttpMock(None, headers={'status': 200})
854 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
855 method = u'POST'
856 request = HttpRequest(
857 http,
858 _postproc_none,
859 uri,
860 method=method,
861 headers={'content-type': ''})
862 request.execute()
863 self.assertEqual('', http.headers.get('content-type'))
864
eesheeshc6425a02016-02-12 15:07:06 +0000865 def test_no_retry_connection_errors(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100866 model = JsonModel()
867 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000868 HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'),
869 model.response,
870 u'https://www.example.com/json_api_endpoint')
871 request._sleep = lambda _x: 0 # do nothing
872 request._rand = lambda: 10
873 with self.assertRaises(socket.error):
874 response = request.execute(num_retries=3)
875
876
877 def test_retry_connection_errors_non_resumable(self):
878 model = JsonModel()
879 request = HttpRequest(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400880 HttpMockWithErrors(4, {'status': '200'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100881 model.response,
882 u'https://www.example.com/json_api_endpoint')
883 request._sleep = lambda _x: 0 # do nothing
884 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400885 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100886 self.assertEqual({u'foo': u'bar'}, response)
887
eesheeshc6425a02016-02-12 15:07:06 +0000888 def test_retry_connection_errors_resumable(self):
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100889 with open(datafile('small.png'), 'rb') as small_png_file:
890 small_png_fd = BytesIO(small_png_file.read())
891 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
892 chunksize=500, resumable=True)
893 model = JsonModel()
894
895 request = HttpRequest(
eesheeshc6425a02016-02-12 15:07:06 +0000896 HttpMockWithErrors(
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400897 4, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100898 model.response,
899 u'https://www.example.com/file_upload',
900 method='POST',
901 resumable=upload)
902 request._sleep = lambda _x: 0 # do nothing
903 request._rand = lambda: 10
Bashir Sadjadc35150f2018-06-25 11:46:09 -0400904 response = request.execute(num_retries=4)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100905 self.assertEqual({u'foo': u'bar'}, response)
906
Joe Gregorio9086bd32013-06-14 16:32:05 -0400907 def test_retry(self):
908 num_retries = 5
eesheeshc6425a02016-02-12 15:07:06 +0000909 resp_seq = [({'status': '500'}, '')] * (num_retries - 3)
910 resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE))
911 resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
912 resp_seq.append(({'status': '429'}, ''))
Joe Gregorio9086bd32013-06-14 16:32:05 -0400913 resp_seq.append(({'status': '200'}, '{}'))
914
915 http = HttpMockSequence(resp_seq)
916 model = JsonModel()
917 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
918 method = u'POST'
919 request = HttpRequest(
920 http,
921 model.response,
922 uri,
923 method=method,
924 body=u'{}',
925 headers={'content-type': 'application/json'})
926
927 sleeptimes = []
928 request._sleep = lambda x: sleeptimes.append(x)
929 request._rand = lambda: 10
930
931 request.execute(num_retries=num_retries)
932
933 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900934 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400935 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
936
eesheeshc6425a02016-02-12 15:07:06 +0000937 def test_no_retry_succeeds(self):
938 num_retries = 5
939 resp_seq = [({'status': '200'}, '{}')] * (num_retries)
940
941 http = HttpMockSequence(resp_seq)
942 model = JsonModel()
943 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
944 method = u'POST'
945 request = HttpRequest(
946 http,
947 model.response,
948 uri,
949 method=method,
950 body=u'{}',
951 headers={'content-type': 'application/json'})
952
953 sleeptimes = []
954 request._sleep = lambda x: sleeptimes.append(x)
955 request._rand = lambda: 10
956
957 request.execute(num_retries=num_retries)
958
959 self.assertEqual(0, len(sleeptimes))
960
Joe Gregorio9086bd32013-06-14 16:32:05 -0400961 def test_no_retry_fails_fast(self):
962 http = HttpMockSequence([
963 ({'status': '500'}, ''),
964 ({'status': '200'}, '{}')
965 ])
966 model = JsonModel()
967 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
968 method = u'POST'
969 request = HttpRequest(
970 http,
971 model.response,
972 uri,
973 method=method,
974 body=u'{}',
975 headers={'content-type': 'application/json'})
976
977 request._rand = lambda: 1.0
eesheeshc6425a02016-02-12 15:07:06 +0000978 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400979
eesheeshc6425a02016-02-12 15:07:06 +0000980 with self.assertRaises(HttpError):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400981 request.execute()
eesheeshc6425a02016-02-12 15:07:06 +0000982 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -0400983
eesheeshc6425a02016-02-12 15:07:06 +0000984 def test_no_retry_403_not_configured_fails_fast(self):
985 http = HttpMockSequence([
986 ({'status': '403'}, NOT_CONFIGURED_RESPONSE),
987 ({'status': '200'}, '{}')
988 ])
989 model = JsonModel()
990 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
991 method = u'POST'
992 request = HttpRequest(
993 http,
994 model.response,
995 uri,
996 method=method,
997 body=u'{}',
998 headers={'content-type': 'application/json'})
999
1000 request._rand = lambda: 1.0
1001 request._sleep = mock.MagicMock()
1002
1003 with self.assertRaises(HttpError):
1004 request.execute()
1005 request._sleep.assert_not_called()
1006
1007 def test_no_retry_403_fails_fast(self):
1008 http = HttpMockSequence([
1009 ({'status': '403'}, ''),
1010 ({'status': '200'}, '{}')
1011 ])
1012 model = JsonModel()
1013 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1014 method = u'POST'
1015 request = HttpRequest(
1016 http,
1017 model.response,
1018 uri,
1019 method=method,
1020 body=u'{}',
1021 headers={'content-type': 'application/json'})
1022
1023 request._rand = lambda: 1.0
1024 request._sleep = mock.MagicMock()
1025
1026 with self.assertRaises(HttpError):
1027 request.execute()
1028 request._sleep.assert_not_called()
1029
1030 def test_no_retry_401_fails_fast(self):
1031 http = HttpMockSequence([
1032 ({'status': '401'}, ''),
1033 ({'status': '200'}, '{}')
1034 ])
1035 model = JsonModel()
1036 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1037 method = u'POST'
1038 request = HttpRequest(
1039 http,
1040 model.response,
1041 uri,
1042 method=method,
1043 body=u'{}',
1044 headers={'content-type': 'application/json'})
1045
1046 request._rand = lambda: 1.0
1047 request._sleep = mock.MagicMock()
1048
1049 with self.assertRaises(HttpError):
1050 request.execute()
1051 request._sleep.assert_not_called()
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001052
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001053 def test_no_retry_403_list_fails(self):
1054 http = HttpMockSequence([
1055 ({'status': '403'}, LIST_NOT_CONFIGURED_RESPONSE),
1056 ({'status': '200'}, '{}')
1057 ])
1058 model = JsonModel()
1059 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
1060 method = u'POST'
1061 request = HttpRequest(
1062 http,
1063 model.response,
1064 uri,
1065 method=method,
1066 body=u'{}',
1067 headers={'content-type': 'application/json'})
1068
1069 request._rand = lambda: 1.0
1070 request._sleep = mock.MagicMock()
1071
1072 with self.assertRaises(HttpError):
1073 request.execute()
1074 request._sleep.assert_not_called()
1075
Joe Gregorio66f57522011-11-30 11:00:00 -05001076class TestBatch(unittest.TestCase):
1077
1078 def setUp(self):
1079 model = JsonModel()
1080 self.request1 = HttpRequest(
1081 None,
1082 model.response,
1083 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1084 method='POST',
1085 body='{}',
1086 headers={'content-type': 'application/json'})
1087
1088 self.request2 = HttpRequest(
1089 None,
1090 model.response,
1091 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001092 method='GET',
1093 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -05001094 headers={'content-type': 'application/json'})
1095
1096
1097 def test_id_to_from_content_id_header(self):
1098 batch = BatchHttpRequest()
1099 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
1100
1101 def test_invalid_content_id_header(self):
1102 batch = BatchHttpRequest()
1103 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
1104 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
1105 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
1106
1107 def test_serialize_request(self):
1108 batch = BatchHttpRequest()
1109 request = HttpRequest(
1110 None,
1111 None,
1112 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1113 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001114 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -05001115 headers={'content-type': 'application/json'},
1116 methodId=None,
1117 resumable=None)
1118 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001119 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001120
Joe Gregoriodd813822012-01-25 10:32:47 -05001121 def test_serialize_request_media_body(self):
1122 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -08001123 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -05001124 body = f.read()
1125 f.close()
1126
1127 request = HttpRequest(
1128 None,
1129 None,
1130 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1131 method='POST',
1132 body=body,
1133 headers={'content-type': 'application/json'},
1134 methodId=None,
1135 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001136 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -05001137 s = batch._serialize_request(request).splitlines()
1138
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001139 def test_serialize_request_no_body(self):
1140 batch = BatchHttpRequest()
1141 request = HttpRequest(
1142 None,
1143 None,
1144 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1145 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -08001146 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001147 headers={'content-type': 'application/json'},
1148 methodId=None,
1149 resumable=None)
1150 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001151 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001152
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001153 def test_serialize_get_request_no_body(self):
1154 batch = BatchHttpRequest()
1155 request = HttpRequest(
1156 None,
1157 None,
1158 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1159 method='GET',
1160 body=None,
1161 headers={'content-type': 'application/json'},
1162 methodId=None,
1163 resumable=None)
1164 s = batch._serialize_request(request).splitlines()
1165 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
1166
Joe Gregorio66f57522011-11-30 11:00:00 -05001167 def test_deserialize_response(self):
1168 batch = BatchHttpRequest()
1169 resp, content = batch._deserialize_response(RESPONSE)
1170
Joe Gregorio654f4a22012-02-09 14:15:44 -05001171 self.assertEqual(200, resp.status)
1172 self.assertEqual('OK', resp.reason)
1173 self.assertEqual(11, resp.version)
1174 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -05001175
1176 def test_new_id(self):
1177 batch = BatchHttpRequest()
1178
1179 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001180 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001181
1182 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001183 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001184
1185 batch.add(self.request1, request_id='3')
1186
1187 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -05001188 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001189
1190 def test_add(self):
1191 batch = BatchHttpRequest()
1192 batch.add(self.request1, request_id='1')
1193 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
1194
Xinan Line2dccec2018-12-07 05:28:33 +09001195 def test_add_fail_for_over_limit(self):
1196 from googleapiclient.http import MAX_BATCH_LIMIT
1197
1198 batch = BatchHttpRequest()
Bu Sun Kimeb4b3e02018-12-12 10:41:03 -08001199 for i in range(0, MAX_BATCH_LIMIT):
Xinan Line2dccec2018-12-07 05:28:33 +09001200 batch.add(HttpRequest(
1201 None,
1202 None,
1203 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1204 method='POST',
1205 body='{}',
1206 headers={'content-type': 'application/json'})
1207 )
1208 self.assertRaises(BatchError, batch.add, self.request1)
1209
Joe Gregorio66f57522011-11-30 11:00:00 -05001210 def test_add_fail_for_resumable(self):
1211 batch = BatchHttpRequest()
1212
1213 upload = MediaFileUpload(
1214 datafile('small.png'), chunksize=500, resumable=True)
1215 self.request1.resumable = upload
ittus5f00cad2016-10-15 10:32:40 +08001216 with self.assertRaises(BatchError) as batch_error:
1217 batch.add(self.request1, request_id='1')
1218 str(batch_error.exception)
Joe Gregorio66f57522011-11-30 11:00:00 -05001219
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +02001220 def test_execute_empty_batch_no_http(self):
1221 batch = BatchHttpRequest()
1222 ret = batch.execute()
1223 self.assertEqual(None, ret)
1224
Joe Gregorio66f57522011-11-30 11:00:00 -05001225 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001226 batch = BatchHttpRequest()
1227 callbacks = Callbacks()
1228
1229 batch.add(self.request1, callback=callbacks.f)
1230 batch.add(self.request2, callback=callbacks.f)
1231 http = HttpMockSequence([
1232 ({'status': '200',
1233 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1234 BATCH_RESPONSE),
1235 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001236 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001237 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1238 self.assertEqual(None, callbacks.exceptions['1'])
1239 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1240 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -05001241
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001242 def test_execute_request_body(self):
1243 batch = BatchHttpRequest()
1244
1245 batch.add(self.request1)
1246 batch.add(self.request2)
1247 http = HttpMockSequence([
1248 ({'status': '200',
1249 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1250 'echo_request_body'),
1251 ])
1252 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001253 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001254 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +09001255 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001256 boundary, _ = e.content.split(None, 1)
1257 self.assertEqual('--', boundary[:2])
1258 parts = e.content.split(boundary)
1259 self.assertEqual(4, len(parts))
1260 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -07001261 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001262 header = parts[1].splitlines()[1]
1263 self.assertEqual('Content-Type: application/http', header)
1264
Chris McDonough3cf5e602018-07-18 16:18:38 -04001265 def test_execute_request_body_with_custom_long_request_ids(self):
1266 batch = BatchHttpRequest()
1267
1268 batch.add(self.request1, request_id='abc'*20)
1269 batch.add(self.request2, request_id='def'*20)
1270 http = HttpMockSequence([
1271 ({'status': '200',
1272 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1273 'echo_request_body'),
1274 ])
1275 try:
1276 batch.execute(http=http)
1277 self.fail('Should raise exception')
1278 except BatchError as e:
1279 boundary, _ = e.content.split(None, 1)
1280 self.assertEqual('--', boundary[:2])
1281 parts = e.content.split(boundary)
1282 self.assertEqual(4, len(parts))
1283 self.assertEqual('', parts[0])
1284 self.assertEqual('--', parts[3].rstrip())
1285 for partindex, request_id in ((1, 'abc'*20), (2, 'def'*20)):
1286 lines = parts[partindex].splitlines()
1287 for n, line in enumerate(lines):
1288 if line.startswith('Content-ID:'):
1289 # assert correct header folding
1290 self.assertTrue(line.endswith('+'), line)
1291 header_continuation = lines[n+1]
1292 self.assertEqual(
1293 header_continuation,
1294 ' %s>' % request_id,
1295 header_continuation
1296 )
1297
Gabriel Garcia23174be2016-05-25 17:28:07 +02001298 def test_execute_initial_refresh_oauth2(self):
1299 batch = BatchHttpRequest()
1300 callbacks = Callbacks()
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -07001301 cred = MockCredentials('Foo', expired=True)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001302
1303 http = HttpMockSequence([
1304 ({'status': '200',
1305 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1306 BATCH_SINGLE_RESPONSE),
1307 ])
1308
1309 cred.authorize(http)
1310
1311 batch.add(self.request1, callback=callbacks.f)
1312 batch.execute(http=http)
1313
1314 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1315 self.assertIsNone(callbacks.exceptions['1'])
1316
1317 self.assertEqual(1, cred._refreshed)
1318
1319 self.assertEqual(1, cred._authorized)
1320
1321 self.assertEqual(1, cred._applied)
1322
Joe Gregorio654f4a22012-02-09 14:15:44 -05001323 def test_execute_refresh_and_retry_on_401(self):
1324 batch = BatchHttpRequest()
1325 callbacks = Callbacks()
1326 cred_1 = MockCredentials('Foo')
1327 cred_2 = MockCredentials('Bar')
1328
1329 http = HttpMockSequence([
1330 ({'status': '200',
1331 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1332 BATCH_RESPONSE_WITH_401),
1333 ({'status': '200',
1334 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1335 BATCH_SINGLE_RESPONSE),
1336 ])
1337
1338 creds_http_1 = HttpMockSequence([])
1339 cred_1.authorize(creds_http_1)
1340
1341 creds_http_2 = HttpMockSequence([])
1342 cred_2.authorize(creds_http_2)
1343
1344 self.request1.http = creds_http_1
1345 self.request2.http = creds_http_2
1346
1347 batch.add(self.request1, callback=callbacks.f)
1348 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001349 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001350
1351 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1352 self.assertEqual(None, callbacks.exceptions['1'])
1353 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
1354 self.assertEqual(None, callbacks.exceptions['2'])
1355
1356 self.assertEqual(1, cred_1._refreshed)
1357 self.assertEqual(0, cred_2._refreshed)
1358
1359 self.assertEqual(1, cred_1._authorized)
1360 self.assertEqual(1, cred_2._authorized)
1361
1362 self.assertEqual(1, cred_2._applied)
1363 self.assertEqual(2, cred_1._applied)
1364
1365 def test_http_errors_passed_to_callback(self):
1366 batch = BatchHttpRequest()
1367 callbacks = Callbacks()
1368 cred_1 = MockCredentials('Foo')
1369 cred_2 = MockCredentials('Bar')
1370
1371 http = HttpMockSequence([
1372 ({'status': '200',
1373 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1374 BATCH_RESPONSE_WITH_401),
1375 ({'status': '200',
1376 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1377 BATCH_RESPONSE_WITH_401),
1378 ])
1379
1380 creds_http_1 = HttpMockSequence([])
1381 cred_1.authorize(creds_http_1)
1382
1383 creds_http_2 = HttpMockSequence([])
1384 cred_2.authorize(creds_http_2)
1385
1386 self.request1.http = creds_http_1
1387 self.request2.http = creds_http_2
1388
1389 batch.add(self.request1, callback=callbacks.f)
1390 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001391 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001392
1393 self.assertEqual(None, callbacks.responses['1'])
1394 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -04001395 self.assertEqual(
1396 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001397 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
1398 self.assertEqual(None, callbacks.exceptions['2'])
1399
Joe Gregorio66f57522011-11-30 11:00:00 -05001400 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -05001401 callbacks = Callbacks()
1402 batch = BatchHttpRequest(callback=callbacks.f)
1403
1404 batch.add(self.request1)
1405 batch.add(self.request2)
1406 http = HttpMockSequence([
1407 ({'status': '200',
1408 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1409 BATCH_RESPONSE),
1410 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001411 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001412 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1413 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001414
Joe Gregorio20b54fb2012-07-26 09:59:35 -04001415 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -04001416 callbacks = Callbacks()
1417 batch = BatchHttpRequest(callback=callbacks.f)
1418
1419 batch.add(self.request1)
1420 batch.add(self.request2)
1421 http = HttpMockSequence([
1422 ({'status': '200',
1423 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
1424 BATCH_ERROR_RESPONSE),
1425 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001426 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -04001427 self.assertEqual({'foo': 42}, callbacks.responses['1'])
1428 expected = ('<HttpError 403 when requesting '
1429 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
1430 '"Access Not Configured">')
1431 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001432
Joe Gregorio5c120db2012-08-23 09:13:55 -04001433
Joe Gregorioba5c7902012-08-03 12:48:16 -04001434class TestRequestUriTooLong(unittest.TestCase):
1435
1436 def test_turn_get_into_post(self):
1437
1438 def _postproc(resp, content):
1439 return content
1440
1441 http = HttpMockSequence([
1442 ({'status': '200'},
1443 'echo_request_body'),
1444 ({'status': '200'},
1445 'echo_request_headers'),
1446 ])
1447
1448 # Send a long query parameter.
1449 query = {
1450 'q': 'a' * MAX_URI_LENGTH + '?&'
1451 }
1452 req = HttpRequest(
1453 http,
1454 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001455 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001456 method='GET',
1457 body=None,
1458 headers={},
1459 methodId='foo',
1460 resumable=None)
1461
1462 # Query parameters should be sent in the body.
1463 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001464 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001465
1466 # Extra headers should be set.
1467 response = req.execute()
1468 self.assertEqual('GET', response['x-http-method-override'])
1469 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1470 self.assertEqual(
1471 'application/x-www-form-urlencoded', response['content-type'])
1472
Joe Gregorio5c120db2012-08-23 09:13:55 -04001473
1474class TestStreamSlice(unittest.TestCase):
1475 """Test _StreamSlice."""
1476
1477 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001478 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001479
1480 def test_read(self):
1481 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001482 self.assertEqual(b'', s.read(0))
1483 self.assertEqual(b'0', s.read(1))
1484 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001485
1486 def test_read_too_much(self):
1487 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001488 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001489
1490 def test_read_all(self):
1491 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001492 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001493
Ali Afshar164f37e2013-01-07 14:05:45 -08001494
1495class TestResponseCallback(unittest.TestCase):
1496 """Test adding callbacks to responses."""
1497
1498 def test_ensure_response_callback(self):
1499 m = JsonModel()
1500 request = HttpRequest(
1501 None,
1502 m.response,
1503 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1504 method='POST',
1505 body='{}',
1506 headers={'content-type': 'application/json'})
1507 h = HttpMockSequence([ ({'status': 200}, '{}')])
1508 responses = []
1509 def _on_response(resp, responses=responses):
1510 responses.append(resp)
1511 request.add_response_callback(_on_response)
1512 request.execute(http=h)
1513 self.assertEqual(1, len(responses))
1514
1515
Craig Gurnik8e55b762015-01-20 15:00:10 -05001516class TestHttpMock(unittest.TestCase):
1517 def test_default_response_headers(self):
1518 http = HttpMock(datafile('zoo.json'))
1519 resp, content = http.request("http://example.com")
1520 self.assertEqual(resp.status, 200)
1521
Alan Briolat26b01002015-08-14 00:13:57 +01001522 def test_error_response(self):
1523 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1524 model = JsonModel()
1525 request = HttpRequest(
1526 http,
1527 model.response,
1528 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1529 method='GET',
1530 headers={})
1531 self.assertRaises(HttpError, request.execute)
1532
Craig Gurnik8e55b762015-01-20 15:00:10 -05001533
Igor Maravić22435292017-01-19 22:28:22 +01001534class TestHttpBuild(unittest.TestCase):
1535 original_socket_default_timeout = None
1536
1537 @classmethod
1538 def setUpClass(cls):
1539 cls.original_socket_default_timeout = socket.getdefaulttimeout()
1540
1541 @classmethod
1542 def tearDownClass(cls):
1543 socket.setdefaulttimeout(cls.original_socket_default_timeout)
1544
1545 def test_build_http_sets_default_timeout_if_none_specified(self):
1546 socket.setdefaulttimeout(None)
1547 http = build_http()
1548 self.assertIsInstance(http.timeout, int)
1549 self.assertGreater(http.timeout, 0)
1550
1551 def test_build_http_default_timeout_can_be_overridden(self):
1552 socket.setdefaulttimeout(1.5)
1553 http = build_http()
1554 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
1555
1556 def test_build_http_default_timeout_can_be_set_to_zero(self):
1557 socket.setdefaulttimeout(0)
1558 http = build_http()
1559 self.assertEquals(http.timeout, 0)
1560
1561
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001562if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001563 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001564 unittest.main()