blob: 943d581d8f05bf9ecf1def52384e69668b839b50 [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
Joe Gregoriod0bd3882011-11-22 09:49:47 -050034import os
Pat Ferate497a90f2015-03-09 09:52:54 -070035import unittest2 as unittest
Joe Gregorio9086bd32013-06-14 16:32:05 -040036import random
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +010037import ssl
Joe Gregorio9086bd32013-06-14 16:32:05 -040038import time
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050039
John Asmuth864311d2014-04-24 15:46:08 -040040from googleapiclient.discovery import build
41from googleapiclient.errors import BatchError
42from googleapiclient.errors import HttpError
43from googleapiclient.errors import InvalidChunkSizeError
44from googleapiclient.http import BatchHttpRequest
45from googleapiclient.http import HttpMock
46from googleapiclient.http import HttpMockSequence
47from googleapiclient.http import HttpRequest
48from googleapiclient.http import MAX_URI_LENGTH
49from googleapiclient.http import MediaFileUpload
50from googleapiclient.http import MediaInMemoryUpload
51from googleapiclient.http import MediaIoBaseDownload
52from googleapiclient.http import MediaIoBaseUpload
53from googleapiclient.http import MediaUpload
54from googleapiclient.http import _StreamSlice
55from googleapiclient.http import set_user_agent
56from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050057from oauth2client.client import Credentials
58
59
60class MockCredentials(Credentials):
61 """Mock class for all Credentials objects."""
62 def __init__(self, bearer_token):
63 super(MockCredentials, self).__init__()
64 self._authorized = 0
65 self._refreshed = 0
66 self._applied = 0
67 self._bearer_token = bearer_token
68
69 def authorize(self, http):
70 self._authorized += 1
71
72 request_orig = http.request
73
74 # The closure that will replace 'httplib2.Http.request'.
75 def new_request(uri, method='GET', body=None, headers=None,
76 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
77 connection_type=None):
78 # Modify the request headers to add the appropriate
79 # Authorization header.
80 if headers is None:
81 headers = {}
82 self.apply(headers)
83
84 resp, content = request_orig(uri, method, body, headers,
85 redirections, connection_type)
86
87 return resp, content
88
89 # Replace the request method with our own closure.
90 http.request = new_request
91
92 # Set credentials as a property of the request method.
93 setattr(http.request, 'credentials', self)
94
95 return http
96
97 def refresh(self, http):
98 self._refreshed += 1
99
100 def apply(self, headers):
101 self._applied += 1
102 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500103
104
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100105class HttpMockWithSSLErrors(object):
106 def __init__(self, num_errors, success_json, success_data):
107 self.num_errors = num_errors
108 self.success_json = success_json
109 self.success_data = success_data
110
111 def request(self, *args, **kwargs):
112 if not self.num_errors:
113 return httplib2.Response(self.success_json), self.success_data
114 else:
115 self.num_errors -= 1
116 raise ssl.SSLError()
117
118
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500119DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
120
121
122def datafile(filename):
123 return os.path.join(DATA_DIR, filename)
124
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500125class TestUserAgent(unittest.TestCase):
126
127 def test_set_user_agent(self):
128 http = HttpMockSequence([
129 ({'status': '200'}, 'echo_request_headers'),
130 ])
131
132 http = set_user_agent(http, "my_app/5.5")
133 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500134 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500135
136 def test_set_user_agent_nested(self):
137 http = HttpMockSequence([
138 ({'status': '200'}, 'echo_request_headers'),
139 ])
140
141 http = set_user_agent(http, "my_app/5.5")
142 http = set_user_agent(http, "my_library/0.1")
143 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500144 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500145
Joe Gregorio910b9b12012-06-12 09:36:30 -0400146
147class TestMediaUpload(unittest.TestCase):
148
Nam T. Nguyendc136312015-12-01 10:18:56 -0800149 def test_media_file_upload_mimetype_detection(self):
150 upload = MediaFileUpload(datafile('small.png'))
151 self.assertEqual('image/png', upload.mimetype())
152
153 upload = MediaFileUpload(datafile('empty'))
154 self.assertEqual('application/octet-stream', upload.mimetype())
155
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500156 def test_media_file_upload_to_from_json(self):
157 upload = MediaFileUpload(
158 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500159 self.assertEqual('image/png', upload.mimetype())
160 self.assertEqual(190, upload.size())
161 self.assertEqual(True, upload.resumable())
162 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800163 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500164
165 json = upload.to_json()
166 new_upload = MediaUpload.new_from_json(json)
167
Joe Gregorio654f4a22012-02-09 14:15:44 -0500168 self.assertEqual('image/png', new_upload.mimetype())
169 self.assertEqual(190, new_upload.size())
170 self.assertEqual(True, new_upload.resumable())
171 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800172 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500173
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400174 def test_media_file_upload_raises_on_invalid_chunksize(self):
175 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
176 datafile('small.png'), mimetype='image/png', chunksize=-2,
177 resumable=True)
178
Ali Afshar1cb6b672012-03-12 08:46:14 -0400179 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800180 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400181 resumable=True)
182 self.assertEqual('text/plain', media.mimetype())
183 self.assertEqual(10, media.chunksize())
184 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800185 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400186 self.assertEqual(6, media.size())
187
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500188 def test_http_request_to_from_json(self):
189
190 def _postproc(*kwargs):
191 pass
192
193 http = httplib2.Http()
194 media_upload = MediaFileUpload(
195 datafile('small.png'), chunksize=500, resumable=True)
196 req = HttpRequest(
197 http,
198 _postproc,
199 'http://example.com',
200 method='POST',
201 body='{}',
202 headers={'content-type': 'multipart/related; boundary="---flubber"'},
203 methodId='foo',
204 resumable=media_upload)
205
206 json = req.to_json()
207 new_req = HttpRequest.from_json(json, http, _postproc)
208
Joe Gregorio654f4a22012-02-09 14:15:44 -0500209 self.assertEqual({'content-type':
210 'multipart/related; boundary="---flubber"'},
211 new_req.headers)
212 self.assertEqual('http://example.com', new_req.uri)
213 self.assertEqual('{}', new_req.body)
214 self.assertEqual(http, new_req.http)
215 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500216
Joe Gregorio9086bd32013-06-14 16:32:05 -0400217 self.assertEqual(random.random, new_req._rand)
218 self.assertEqual(time.sleep, new_req._sleep)
219
Joe Gregorio910b9b12012-06-12 09:36:30 -0400220
221class TestMediaIoBaseUpload(unittest.TestCase):
222
223 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800224 fd = FileIO(datafile('small.png'), 'r')
225 upload = MediaIoBaseUpload(
226 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
227 self.assertEqual('image/png', upload.mimetype())
228 self.assertEqual(190, upload.size())
229 self.assertEqual(True, upload.resumable())
230 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800231 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400232
233 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800234 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400235 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400236 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400237 self.assertEqual('image/png', upload.mimetype())
238 self.assertEqual(190, upload.size())
239 self.assertEqual(True, upload.resumable())
240 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800241 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400242 f.close()
243
244 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800245 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400246 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400247
248 try:
249 json = upload.to_json()
250 self.fail('MediaIoBaseUpload should not be serializable.')
251 except NotImplementedError:
252 pass
253
Pat Feratec6050872015-03-03 18:24:59 -0800254 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400255 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800256 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800257 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400258 f.close()
259
260 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400261 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400262 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400263 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400264 self.assertEqual(True, upload.resumable())
265 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800266 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400267 f.close()
268
269 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800270 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800271 fd = BytesIO(f.read())
272 upload = MediaIoBaseUpload(
273 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
274 self.assertEqual('image/png', upload.mimetype())
275 self.assertEqual(190, upload.size())
276 self.assertEqual(True, upload.resumable())
277 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800278 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400279
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400280 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800281 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800282 fd = BytesIO(f.read())
283 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
284 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400285
286 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800287 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800288 upload = MediaIoBaseUpload(
289 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
290 self.assertEqual(True, upload.has_stream())
291 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400292
Joe Gregorio9086bd32013-06-14 16:32:05 -0400293 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800294 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800295 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400296 upload = MediaIoBaseUpload(
297 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
298
299 # Simulate 5XXs for both the request that creates the resumable upload and
300 # the upload itself.
301 http = HttpMockSequence([
302 ({'status': '500'}, ''),
303 ({'status': '500'}, ''),
304 ({'status': '503'}, ''),
305 ({'status': '200', 'location': 'location'}, ''),
306 ({'status': '500'}, ''),
307 ({'status': '500'}, ''),
308 ({'status': '503'}, ''),
309 ({'status': '200'}, '{}'),
310 ])
311
312 model = JsonModel()
313 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
314 method = u'POST'
315 request = HttpRequest(
316 http,
317 model.response,
318 uri,
319 method=method,
320 headers={},
321 resumable=upload)
322
323 sleeptimes = []
324 request._sleep = lambda x: sleeptimes.append(x)
325 request._rand = lambda: 10
326
327 request.execute(num_retries=3)
328 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
329
Joe Gregorio910b9b12012-06-12 09:36:30 -0400330
Joe Gregorio708388c2012-06-15 13:43:04 -0400331class TestMediaIoBaseDownload(unittest.TestCase):
332
333 def setUp(self):
334 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400335 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400336 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800337 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400338
339 def test_media_io_base_download(self):
340 self.request.http = HttpMockSequence([
341 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800342 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400343 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800344 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400345 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400346 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400347
348 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400349 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400350
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400351 self.assertEqual(self.fd, download._fd)
352 self.assertEqual(3, download._chunksize)
353 self.assertEqual(0, download._progress)
354 self.assertEqual(None, download._total_size)
355 self.assertEqual(False, download._done)
356 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400357
358 status, done = download.next_chunk()
359
Pat Ferate2b140222015-03-03 18:05:11 -0800360 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400361 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400362 self.assertEqual(3, download._progress)
363 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400364 self.assertEqual(3, status.resumable_progress)
365
366 status, done = download.next_chunk()
367
Pat Ferate2b140222015-03-03 18:05:11 -0800368 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400369 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400370 self.assertEqual(5, download._progress)
371 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400372
373 def test_media_io_base_download_handle_redirects(self):
374 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400375 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800376 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400377 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800378 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400379 ])
380
381 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400382 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400383
384 status, done = download.next_chunk()
385
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400386 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400387
388 def test_media_io_base_download_handle_4xx(self):
389 self.request.http = HttpMockSequence([
390 ({'status': '400'}, ''),
391 ])
392
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
396 try:
397 status, done = download.next_chunk()
398 self.fail('Should raise an exception')
399 except HttpError:
400 pass
401
402 # Even after raising an exception we can pick up where we left off.
403 self.request.http = HttpMockSequence([
404 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800405 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400406 ])
407
408 status, done = download.next_chunk()
409
Pat Ferate2b140222015-03-03 18:05:11 -0800410 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400411
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100412 def test_media_io_base_download_retries_ssl_errors(self):
413 self.request.http = HttpMockWithSSLErrors(
414 3, {'status': '200', 'content-range': '0-2/3'}, b'123')
415
416 download = MediaIoBaseDownload(
417 fd=self.fd, request=self.request, chunksize=3)
418 download._sleep = lambda _x: 0 # do nothing
419 download._rand = lambda: 10
420
421 status, done = download.next_chunk(num_retries=3)
422
423 self.assertEqual(self.fd.getvalue(), b'123')
424 self.assertEqual(True, done)
425
Joe Gregorio9086bd32013-06-14 16:32:05 -0400426 def test_media_io_base_download_retries_5xx(self):
427 self.request.http = HttpMockSequence([
428 ({'status': '500'}, ''),
429 ({'status': '500'}, ''),
430 ({'status': '500'}, ''),
431 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800432 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400433 ({'status': '503'}, ''),
434 ({'status': '503'}, ''),
435 ({'status': '503'}, ''),
436 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800437 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400438 ])
439
440 download = MediaIoBaseDownload(
441 fd=self.fd, request=self.request, chunksize=3)
442
443 self.assertEqual(self.fd, download._fd)
444 self.assertEqual(3, download._chunksize)
445 self.assertEqual(0, download._progress)
446 self.assertEqual(None, download._total_size)
447 self.assertEqual(False, download._done)
448 self.assertEqual(self.request.uri, download._uri)
449
450 # Set time.sleep and random.random stubs.
451 sleeptimes = []
452 download._sleep = lambda x: sleeptimes.append(x)
453 download._rand = lambda: 10
454
455 status, done = download.next_chunk(num_retries=3)
456
457 # Check for exponential backoff using the rand function above.
458 self.assertEqual([20, 40, 80], sleeptimes)
459
Pat Ferate2b140222015-03-03 18:05:11 -0800460 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400461 self.assertEqual(False, done)
462 self.assertEqual(3, download._progress)
463 self.assertEqual(5, download._total_size)
464 self.assertEqual(3, status.resumable_progress)
465
466 # Reset time.sleep stub.
467 del sleeptimes[0:len(sleeptimes)]
468
469 status, done = download.next_chunk(num_retries=3)
470
471 # Check for exponential backoff using the rand function above.
472 self.assertEqual([20, 40, 80], sleeptimes)
473
Pat Ferate2b140222015-03-03 18:05:11 -0800474 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400475 self.assertEqual(True, done)
476 self.assertEqual(5, download._progress)
477 self.assertEqual(5, download._total_size)
478
Joe Gregorio66f57522011-11-30 11:00:00 -0500479EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
480Content-Type: application/json
481MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500482Host: www.googleapis.com
483content-length: 2\r\n\r\n{}"""
484
485
486NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
487Content-Type: application/json
488MIME-Version: 1.0
489Host: www.googleapis.com
490content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500491
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400492NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
493Content-Type: application/json
494MIME-Version: 1.0
495Host: www.googleapis.com\r\n\r\n"""
496
Joe Gregorio66f57522011-11-30 11:00:00 -0500497
498RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400499Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500500Content-Length: 14
501ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
502
503
INADA Naoki09157612015-03-25 01:51:03 +0900504BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500505Content-Type: application/http
506Content-Transfer-Encoding: binary
507Content-ID: <randomness+1>
508
509HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400510Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500511Content-Length: 14
512ETag: "etag/pony"\r\n\r\n{"foo": 42}
513
514--batch_foobarbaz
515Content-Type: application/http
516Content-Transfer-Encoding: binary
517Content-ID: <randomness+2>
518
519HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400520Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500521Content-Length: 14
522ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
523--batch_foobarbaz--"""
524
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500525
INADA Naoki09157612015-03-25 01:51:03 +0900526BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400527Content-Type: application/http
528Content-Transfer-Encoding: binary
529Content-ID: <randomness+1>
530
531HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400532Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400533Content-Length: 14
534ETag: "etag/pony"\r\n\r\n{"foo": 42}
535
536--batch_foobarbaz
537Content-Type: application/http
538Content-Transfer-Encoding: binary
539Content-ID: <randomness+2>
540
541HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400542Content-Type: application/json
543Content-Length: 245
544ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400545 "error": {
546 "errors": [
547 {
548 "domain": "usageLimits",
549 "reason": "accessNotConfigured",
550 "message": "Access Not Configured",
551 "debugInfo": "QuotaState: BLOCKED"
552 }
553 ],
554 "code": 403,
555 "message": "Access Not Configured"
556 }
557}
558
559--batch_foobarbaz--"""
560
561
INADA Naoki09157612015-03-25 01:51:03 +0900562BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500563Content-Type: application/http
564Content-Transfer-Encoding: binary
565Content-ID: <randomness+1>
566
Joe Gregorioc752e332012-07-11 14:43:52 -0400567HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400568Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500569Content-Length: 14
570ETag: "etag/pony"\r\n\r\n{"error": {"message":
571 "Authorizaton failed."}}
572
573--batch_foobarbaz
574Content-Type: application/http
575Content-Transfer-Encoding: binary
576Content-ID: <randomness+2>
577
578HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400579Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500580Content-Length: 14
581ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
582--batch_foobarbaz--"""
583
584
INADA Naoki09157612015-03-25 01:51:03 +0900585BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500586Content-Type: application/http
587Content-Transfer-Encoding: binary
588Content-ID: <randomness+1>
589
590HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400591Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500592Content-Length: 14
593ETag: "etag/pony"\r\n\r\n{"foo": 42}
594--batch_foobarbaz--"""
595
596class Callbacks(object):
597 def __init__(self):
598 self.responses = {}
599 self.exceptions = {}
600
601 def f(self, request_id, response, exception):
602 self.responses[request_id] = response
603 self.exceptions[request_id] = exception
604
605
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500606class TestHttpRequest(unittest.TestCase):
607 def test_unicode(self):
608 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
609 model = JsonModel()
610 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
611 method = u'POST'
612 request = HttpRequest(
613 http,
614 model.response,
615 uri,
616 method=method,
617 body=u'{}',
618 headers={'content-type': 'application/json'})
619 request.execute()
620 self.assertEqual(uri, http.uri)
621 self.assertEqual(str, type(http.uri))
622 self.assertEqual(method, http.method)
623 self.assertEqual(str, type(http.method))
624
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100625 def test_retry_ssl_errors_non_resumable(self):
626 model = JsonModel()
627 request = HttpRequest(
628 HttpMockWithSSLErrors(3, {'status': '200'}, '{"foo": "bar"}'),
629 model.response,
630 u'https://www.example.com/json_api_endpoint')
631 request._sleep = lambda _x: 0 # do nothing
632 request._rand = lambda: 10
633 response = request.execute(num_retries=3)
634 self.assertEqual({u'foo': u'bar'}, response)
635
636 def test_retry_ssl_errors_resumable(self):
637 with open(datafile('small.png'), 'rb') as small_png_file:
638 small_png_fd = BytesIO(small_png_file.read())
639 upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png',
640 chunksize=500, resumable=True)
641 model = JsonModel()
642
643 request = HttpRequest(
644 HttpMockWithSSLErrors(
645 3, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'),
646 model.response,
647 u'https://www.example.com/file_upload',
648 method='POST',
649 resumable=upload)
650 request._sleep = lambda _x: 0 # do nothing
651 request._rand = lambda: 10
652 response = request.execute(num_retries=3)
653 self.assertEqual({u'foo': u'bar'}, response)
654
Joe Gregorio9086bd32013-06-14 16:32:05 -0400655 def test_retry(self):
656 num_retries = 5
657 resp_seq = [({'status': '500'}, '')] * num_retries
658 resp_seq.append(({'status': '200'}, '{}'))
659
660 http = HttpMockSequence(resp_seq)
661 model = JsonModel()
662 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
663 method = u'POST'
664 request = HttpRequest(
665 http,
666 model.response,
667 uri,
668 method=method,
669 body=u'{}',
670 headers={'content-type': 'application/json'})
671
672 sleeptimes = []
673 request._sleep = lambda x: sleeptimes.append(x)
674 request._rand = lambda: 10
675
676 request.execute(num_retries=num_retries)
677
678 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900679 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400680 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
681
682 def test_no_retry_fails_fast(self):
683 http = HttpMockSequence([
684 ({'status': '500'}, ''),
685 ({'status': '200'}, '{}')
686 ])
687 model = JsonModel()
688 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
689 method = u'POST'
690 request = HttpRequest(
691 http,
692 model.response,
693 uri,
694 method=method,
695 body=u'{}',
696 headers={'content-type': 'application/json'})
697
698 request._rand = lambda: 1.0
699 request._sleep = lambda _: self.fail('sleep should not have been called.')
700
701 try:
702 request.execute()
703 self.fail('Should have raised an exception.')
704 except HttpError:
705 pass
706
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500707
Joe Gregorio66f57522011-11-30 11:00:00 -0500708class TestBatch(unittest.TestCase):
709
710 def setUp(self):
711 model = JsonModel()
712 self.request1 = HttpRequest(
713 None,
714 model.response,
715 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
716 method='POST',
717 body='{}',
718 headers={'content-type': 'application/json'})
719
720 self.request2 = HttpRequest(
721 None,
722 model.response,
723 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500724 method='GET',
725 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500726 headers={'content-type': 'application/json'})
727
728
729 def test_id_to_from_content_id_header(self):
730 batch = BatchHttpRequest()
731 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
732
733 def test_invalid_content_id_header(self):
734 batch = BatchHttpRequest()
735 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
736 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
737 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
738
739 def test_serialize_request(self):
740 batch = BatchHttpRequest()
741 request = HttpRequest(
742 None,
743 None,
744 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
745 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800746 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -0500747 headers={'content-type': 'application/json'},
748 methodId=None,
749 resumable=None)
750 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500751 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500752
Joe Gregoriodd813822012-01-25 10:32:47 -0500753 def test_serialize_request_media_body(self):
754 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -0800755 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -0500756 body = f.read()
757 f.close()
758
759 request = HttpRequest(
760 None,
761 None,
762 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
763 method='POST',
764 body=body,
765 headers={'content-type': 'application/json'},
766 methodId=None,
767 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500768 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -0500769 s = batch._serialize_request(request).splitlines()
770
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500771 def test_serialize_request_no_body(self):
772 batch = BatchHttpRequest()
773 request = HttpRequest(
774 None,
775 None,
776 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
777 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800778 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500779 headers={'content-type': 'application/json'},
780 methodId=None,
781 resumable=None)
782 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500783 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500784
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400785 def test_serialize_get_request_no_body(self):
786 batch = BatchHttpRequest()
787 request = HttpRequest(
788 None,
789 None,
790 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
791 method='GET',
792 body=None,
793 headers={'content-type': 'application/json'},
794 methodId=None,
795 resumable=None)
796 s = batch._serialize_request(request).splitlines()
797 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
798
Joe Gregorio66f57522011-11-30 11:00:00 -0500799 def test_deserialize_response(self):
800 batch = BatchHttpRequest()
801 resp, content = batch._deserialize_response(RESPONSE)
802
Joe Gregorio654f4a22012-02-09 14:15:44 -0500803 self.assertEqual(200, resp.status)
804 self.assertEqual('OK', resp.reason)
805 self.assertEqual(11, resp.version)
806 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -0500807
808 def test_new_id(self):
809 batch = BatchHttpRequest()
810
811 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500812 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500813
814 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500815 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500816
817 batch.add(self.request1, request_id='3')
818
819 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500820 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500821
822 def test_add(self):
823 batch = BatchHttpRequest()
824 batch.add(self.request1, request_id='1')
825 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
826
827 def test_add_fail_for_resumable(self):
828 batch = BatchHttpRequest()
829
830 upload = MediaFileUpload(
831 datafile('small.png'), chunksize=500, resumable=True)
832 self.request1.resumable = upload
833 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
834
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +0200835 def test_execute_empty_batch_no_http(self):
836 batch = BatchHttpRequest()
837 ret = batch.execute()
838 self.assertEqual(None, ret)
839
Joe Gregorio66f57522011-11-30 11:00:00 -0500840 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500841 batch = BatchHttpRequest()
842 callbacks = Callbacks()
843
844 batch.add(self.request1, callback=callbacks.f)
845 batch.add(self.request2, callback=callbacks.f)
846 http = HttpMockSequence([
847 ({'status': '200',
848 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
849 BATCH_RESPONSE),
850 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400851 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500852 self.assertEqual({'foo': 42}, callbacks.responses['1'])
853 self.assertEqual(None, callbacks.exceptions['1'])
854 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
855 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -0500856
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500857 def test_execute_request_body(self):
858 batch = BatchHttpRequest()
859
860 batch.add(self.request1)
861 batch.add(self.request2)
862 http = HttpMockSequence([
863 ({'status': '200',
864 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
865 'echo_request_body'),
866 ])
867 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400868 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500869 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +0900870 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500871 boundary, _ = e.content.split(None, 1)
872 self.assertEqual('--', boundary[:2])
873 parts = e.content.split(boundary)
874 self.assertEqual(4, len(parts))
875 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -0700876 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500877 header = parts[1].splitlines()[1]
878 self.assertEqual('Content-Type: application/http', header)
879
Joe Gregorio654f4a22012-02-09 14:15:44 -0500880 def test_execute_refresh_and_retry_on_401(self):
881 batch = BatchHttpRequest()
882 callbacks = Callbacks()
883 cred_1 = MockCredentials('Foo')
884 cred_2 = MockCredentials('Bar')
885
886 http = HttpMockSequence([
887 ({'status': '200',
888 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
889 BATCH_RESPONSE_WITH_401),
890 ({'status': '200',
891 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
892 BATCH_SINGLE_RESPONSE),
893 ])
894
895 creds_http_1 = HttpMockSequence([])
896 cred_1.authorize(creds_http_1)
897
898 creds_http_2 = HttpMockSequence([])
899 cred_2.authorize(creds_http_2)
900
901 self.request1.http = creds_http_1
902 self.request2.http = creds_http_2
903
904 batch.add(self.request1, callback=callbacks.f)
905 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400906 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500907
908 self.assertEqual({'foo': 42}, callbacks.responses['1'])
909 self.assertEqual(None, callbacks.exceptions['1'])
910 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
911 self.assertEqual(None, callbacks.exceptions['2'])
912
913 self.assertEqual(1, cred_1._refreshed)
914 self.assertEqual(0, cred_2._refreshed)
915
916 self.assertEqual(1, cred_1._authorized)
917 self.assertEqual(1, cred_2._authorized)
918
919 self.assertEqual(1, cred_2._applied)
920 self.assertEqual(2, cred_1._applied)
921
922 def test_http_errors_passed_to_callback(self):
923 batch = BatchHttpRequest()
924 callbacks = Callbacks()
925 cred_1 = MockCredentials('Foo')
926 cred_2 = MockCredentials('Bar')
927
928 http = HttpMockSequence([
929 ({'status': '200',
930 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
931 BATCH_RESPONSE_WITH_401),
932 ({'status': '200',
933 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
934 BATCH_RESPONSE_WITH_401),
935 ])
936
937 creds_http_1 = HttpMockSequence([])
938 cred_1.authorize(creds_http_1)
939
940 creds_http_2 = HttpMockSequence([])
941 cred_2.authorize(creds_http_2)
942
943 self.request1.http = creds_http_1
944 self.request2.http = creds_http_2
945
946 batch.add(self.request1, callback=callbacks.f)
947 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400948 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500949
950 self.assertEqual(None, callbacks.responses['1'])
951 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -0400952 self.assertEqual(
953 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500954 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
955 self.assertEqual(None, callbacks.exceptions['2'])
956
Joe Gregorio66f57522011-11-30 11:00:00 -0500957 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500958 callbacks = Callbacks()
959 batch = BatchHttpRequest(callback=callbacks.f)
960
961 batch.add(self.request1)
962 batch.add(self.request2)
963 http = HttpMockSequence([
964 ({'status': '200',
965 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
966 BATCH_RESPONSE),
967 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400968 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500969 self.assertEqual({'foo': 42}, callbacks.responses['1'])
970 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500971
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400972 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -0400973 callbacks = Callbacks()
974 batch = BatchHttpRequest(callback=callbacks.f)
975
976 batch.add(self.request1)
977 batch.add(self.request2)
978 http = HttpMockSequence([
979 ({'status': '200',
980 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
981 BATCH_ERROR_RESPONSE),
982 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400983 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -0400984 self.assertEqual({'foo': 42}, callbacks.responses['1'])
985 expected = ('<HttpError 403 when requesting '
986 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
987 '"Access Not Configured">')
988 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -0500989
Joe Gregorio5c120db2012-08-23 09:13:55 -0400990
Joe Gregorioba5c7902012-08-03 12:48:16 -0400991class TestRequestUriTooLong(unittest.TestCase):
992
993 def test_turn_get_into_post(self):
994
995 def _postproc(resp, content):
996 return content
997
998 http = HttpMockSequence([
999 ({'status': '200'},
1000 'echo_request_body'),
1001 ({'status': '200'},
1002 'echo_request_headers'),
1003 ])
1004
1005 # Send a long query parameter.
1006 query = {
1007 'q': 'a' * MAX_URI_LENGTH + '?&'
1008 }
1009 req = HttpRequest(
1010 http,
1011 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -08001012 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -04001013 method='GET',
1014 body=None,
1015 headers={},
1016 methodId='foo',
1017 resumable=None)
1018
1019 # Query parameters should be sent in the body.
1020 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +09001021 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001022
1023 # Extra headers should be set.
1024 response = req.execute()
1025 self.assertEqual('GET', response['x-http-method-override'])
1026 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
1027 self.assertEqual(
1028 'application/x-www-form-urlencoded', response['content-type'])
1029
Joe Gregorio5c120db2012-08-23 09:13:55 -04001030
1031class TestStreamSlice(unittest.TestCase):
1032 """Test _StreamSlice."""
1033
1034 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -08001035 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -04001036
1037 def test_read(self):
1038 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001039 self.assertEqual(b'', s.read(0))
1040 self.assertEqual(b'0', s.read(1))
1041 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001042
1043 def test_read_too_much(self):
1044 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -08001045 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001046
1047 def test_read_all(self):
1048 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -08001049 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001050
Ali Afshar164f37e2013-01-07 14:05:45 -08001051
1052class TestResponseCallback(unittest.TestCase):
1053 """Test adding callbacks to responses."""
1054
1055 def test_ensure_response_callback(self):
1056 m = JsonModel()
1057 request = HttpRequest(
1058 None,
1059 m.response,
1060 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1061 method='POST',
1062 body='{}',
1063 headers={'content-type': 'application/json'})
1064 h = HttpMockSequence([ ({'status': 200}, '{}')])
1065 responses = []
1066 def _on_response(resp, responses=responses):
1067 responses.append(resp)
1068 request.add_response_callback(_on_response)
1069 request.execute(http=h)
1070 self.assertEqual(1, len(responses))
1071
1072
Craig Gurnik8e55b762015-01-20 15:00:10 -05001073class TestHttpMock(unittest.TestCase):
1074 def test_default_response_headers(self):
1075 http = HttpMock(datafile('zoo.json'))
1076 resp, content = http.request("http://example.com")
1077 self.assertEqual(resp.status, 200)
1078
Alan Briolat26b01002015-08-14 00:13:57 +01001079 def test_error_response(self):
1080 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1081 model = JsonModel()
1082 request = HttpRequest(
1083 http,
1084 model.response,
1085 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1086 method='GET',
1087 headers={})
1088 self.assertRaises(HttpError, request.execute)
1089
Craig Gurnik8e55b762015-01-20 15:00:10 -05001090
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001091if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001092 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001093 unittest.main()