blob: 6fb8305563c51c5caf32548c232ecf65d37487c0 [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
Joe Gregorio9086bd32013-06-14 16:32:05 -040037import time
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050038
John Asmuth864311d2014-04-24 15:46:08 -040039from googleapiclient.discovery import build
40from googleapiclient.errors import BatchError
41from googleapiclient.errors import HttpError
42from googleapiclient.errors import InvalidChunkSizeError
43from googleapiclient.http import BatchHttpRequest
44from googleapiclient.http import HttpMock
45from googleapiclient.http import HttpMockSequence
46from googleapiclient.http import HttpRequest
47from googleapiclient.http import MAX_URI_LENGTH
48from googleapiclient.http import MediaFileUpload
49from googleapiclient.http import MediaInMemoryUpload
50from googleapiclient.http import MediaIoBaseDownload
51from googleapiclient.http import MediaIoBaseUpload
52from googleapiclient.http import MediaUpload
53from googleapiclient.http import _StreamSlice
54from googleapiclient.http import set_user_agent
55from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050056from oauth2client.client import Credentials
57
58
59class MockCredentials(Credentials):
60 """Mock class for all Credentials objects."""
61 def __init__(self, bearer_token):
62 super(MockCredentials, self).__init__()
63 self._authorized = 0
64 self._refreshed = 0
65 self._applied = 0
66 self._bearer_token = bearer_token
67
68 def authorize(self, http):
69 self._authorized += 1
70
71 request_orig = http.request
72
73 # The closure that will replace 'httplib2.Http.request'.
74 def new_request(uri, method='GET', body=None, headers=None,
75 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
76 connection_type=None):
77 # Modify the request headers to add the appropriate
78 # Authorization header.
79 if headers is None:
80 headers = {}
81 self.apply(headers)
82
83 resp, content = request_orig(uri, method, body, headers,
84 redirections, connection_type)
85
86 return resp, content
87
88 # Replace the request method with our own closure.
89 http.request = new_request
90
91 # Set credentials as a property of the request method.
92 setattr(http.request, 'credentials', self)
93
94 return http
95
96 def refresh(self, http):
97 self._refreshed += 1
98
99 def apply(self, headers):
100 self._applied += 1
101 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500102
103
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500104DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
105
106
107def datafile(filename):
108 return os.path.join(DATA_DIR, filename)
109
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500110class TestUserAgent(unittest.TestCase):
111
112 def test_set_user_agent(self):
113 http = HttpMockSequence([
114 ({'status': '200'}, 'echo_request_headers'),
115 ])
116
117 http = set_user_agent(http, "my_app/5.5")
118 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500119 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500120
121 def test_set_user_agent_nested(self):
122 http = HttpMockSequence([
123 ({'status': '200'}, 'echo_request_headers'),
124 ])
125
126 http = set_user_agent(http, "my_app/5.5")
127 http = set_user_agent(http, "my_library/0.1")
128 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500129 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500130
Joe Gregorio910b9b12012-06-12 09:36:30 -0400131
132class TestMediaUpload(unittest.TestCase):
133
Nam T. Nguyendc136312015-12-01 10:18:56 -0800134 def test_media_file_upload_mimetype_detection(self):
135 upload = MediaFileUpload(datafile('small.png'))
136 self.assertEqual('image/png', upload.mimetype())
137
138 upload = MediaFileUpload(datafile('empty'))
139 self.assertEqual('application/octet-stream', upload.mimetype())
140
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500141 def test_media_file_upload_to_from_json(self):
142 upload = MediaFileUpload(
143 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500144 self.assertEqual('image/png', upload.mimetype())
145 self.assertEqual(190, upload.size())
146 self.assertEqual(True, upload.resumable())
147 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800148 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500149
150 json = upload.to_json()
151 new_upload = MediaUpload.new_from_json(json)
152
Joe Gregorio654f4a22012-02-09 14:15:44 -0500153 self.assertEqual('image/png', new_upload.mimetype())
154 self.assertEqual(190, new_upload.size())
155 self.assertEqual(True, new_upload.resumable())
156 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800157 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500158
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400159 def test_media_file_upload_raises_on_invalid_chunksize(self):
160 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
161 datafile('small.png'), mimetype='image/png', chunksize=-2,
162 resumable=True)
163
Ali Afshar1cb6b672012-03-12 08:46:14 -0400164 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800165 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400166 resumable=True)
167 self.assertEqual('text/plain', media.mimetype())
168 self.assertEqual(10, media.chunksize())
169 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800170 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400171 self.assertEqual(6, media.size())
172
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500173 def test_http_request_to_from_json(self):
174
175 def _postproc(*kwargs):
176 pass
177
178 http = httplib2.Http()
179 media_upload = MediaFileUpload(
180 datafile('small.png'), chunksize=500, resumable=True)
181 req = HttpRequest(
182 http,
183 _postproc,
184 'http://example.com',
185 method='POST',
186 body='{}',
187 headers={'content-type': 'multipart/related; boundary="---flubber"'},
188 methodId='foo',
189 resumable=media_upload)
190
191 json = req.to_json()
192 new_req = HttpRequest.from_json(json, http, _postproc)
193
Joe Gregorio654f4a22012-02-09 14:15:44 -0500194 self.assertEqual({'content-type':
195 'multipart/related; boundary="---flubber"'},
196 new_req.headers)
197 self.assertEqual('http://example.com', new_req.uri)
198 self.assertEqual('{}', new_req.body)
199 self.assertEqual(http, new_req.http)
200 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500201
Joe Gregorio9086bd32013-06-14 16:32:05 -0400202 self.assertEqual(random.random, new_req._rand)
203 self.assertEqual(time.sleep, new_req._sleep)
204
Joe Gregorio910b9b12012-06-12 09:36:30 -0400205
206class TestMediaIoBaseUpload(unittest.TestCase):
207
208 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800209 fd = FileIO(datafile('small.png'), 'r')
210 upload = MediaIoBaseUpload(
211 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
212 self.assertEqual('image/png', upload.mimetype())
213 self.assertEqual(190, upload.size())
214 self.assertEqual(True, upload.resumable())
215 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800216 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400217
218 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800219 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400220 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400221 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400222 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 Gregorio910b9b12012-06-12 09:36:30 -0400227 f.close()
228
229 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800230 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400231 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400232
233 try:
234 json = upload.to_json()
235 self.fail('MediaIoBaseUpload should not be serializable.')
236 except NotImplementedError:
237 pass
238
Pat Feratec6050872015-03-03 18:24:59 -0800239 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400240 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800241 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800242 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400243 f.close()
244
245 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400246 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400247 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400248 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400249 self.assertEqual(True, upload.resumable())
250 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800251 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400252 f.close()
253
254 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800255 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800256 fd = BytesIO(f.read())
257 upload = MediaIoBaseUpload(
258 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
259 self.assertEqual('image/png', upload.mimetype())
260 self.assertEqual(190, upload.size())
261 self.assertEqual(True, upload.resumable())
262 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800263 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400264
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400265 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800266 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800267 fd = BytesIO(f.read())
268 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
269 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400270
271 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800272 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800273 upload = MediaIoBaseUpload(
274 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
275 self.assertEqual(True, upload.has_stream())
276 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400277
Joe Gregorio9086bd32013-06-14 16:32:05 -0400278 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800279 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800280 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400281 upload = MediaIoBaseUpload(
282 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
283
284 # Simulate 5XXs for both the request that creates the resumable upload and
285 # the upload itself.
286 http = HttpMockSequence([
287 ({'status': '500'}, ''),
288 ({'status': '500'}, ''),
289 ({'status': '503'}, ''),
290 ({'status': '200', 'location': 'location'}, ''),
291 ({'status': '500'}, ''),
292 ({'status': '500'}, ''),
293 ({'status': '503'}, ''),
294 ({'status': '200'}, '{}'),
295 ])
296
297 model = JsonModel()
298 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
299 method = u'POST'
300 request = HttpRequest(
301 http,
302 model.response,
303 uri,
304 method=method,
305 headers={},
306 resumable=upload)
307
308 sleeptimes = []
309 request._sleep = lambda x: sleeptimes.append(x)
310 request._rand = lambda: 10
311
312 request.execute(num_retries=3)
313 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
314
Joe Gregorio910b9b12012-06-12 09:36:30 -0400315
Joe Gregorio708388c2012-06-15 13:43:04 -0400316class TestMediaIoBaseDownload(unittest.TestCase):
317
318 def setUp(self):
319 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400320 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400321 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800322 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400323
324 def test_media_io_base_download(self):
325 self.request.http = HttpMockSequence([
326 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800327 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400328 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800329 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400330 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400331 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400332
333 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400334 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400335
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400336 self.assertEqual(self.fd, download._fd)
337 self.assertEqual(3, download._chunksize)
338 self.assertEqual(0, download._progress)
339 self.assertEqual(None, download._total_size)
340 self.assertEqual(False, download._done)
341 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400342
343 status, done = download.next_chunk()
344
Pat Ferate2b140222015-03-03 18:05:11 -0800345 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400346 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400347 self.assertEqual(3, download._progress)
348 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400349 self.assertEqual(3, status.resumable_progress)
350
351 status, done = download.next_chunk()
352
Pat Ferate2b140222015-03-03 18:05:11 -0800353 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400354 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400355 self.assertEqual(5, download._progress)
356 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400357
358 def test_media_io_base_download_handle_redirects(self):
359 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400360 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800361 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400362 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800363 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400364 ])
365
366 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400367 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400368
369 status, done = download.next_chunk()
370
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400371 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400372
373 def test_media_io_base_download_handle_4xx(self):
374 self.request.http = HttpMockSequence([
375 ({'status': '400'}, ''),
376 ])
377
378 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400379 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400380
381 try:
382 status, done = download.next_chunk()
383 self.fail('Should raise an exception')
384 except HttpError:
385 pass
386
387 # Even after raising an exception we can pick up where we left off.
388 self.request.http = HttpMockSequence([
389 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800390 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400391 ])
392
393 status, done = download.next_chunk()
394
Pat Ferate2b140222015-03-03 18:05:11 -0800395 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400396
Joe Gregorio9086bd32013-06-14 16:32:05 -0400397 def test_media_io_base_download_retries_5xx(self):
398 self.request.http = HttpMockSequence([
399 ({'status': '500'}, ''),
400 ({'status': '500'}, ''),
401 ({'status': '500'}, ''),
402 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800403 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400404 ({'status': '503'}, ''),
405 ({'status': '503'}, ''),
406 ({'status': '503'}, ''),
407 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800408 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400409 ])
410
411 download = MediaIoBaseDownload(
412 fd=self.fd, request=self.request, chunksize=3)
413
414 self.assertEqual(self.fd, download._fd)
415 self.assertEqual(3, download._chunksize)
416 self.assertEqual(0, download._progress)
417 self.assertEqual(None, download._total_size)
418 self.assertEqual(False, download._done)
419 self.assertEqual(self.request.uri, download._uri)
420
421 # Set time.sleep and random.random stubs.
422 sleeptimes = []
423 download._sleep = lambda x: sleeptimes.append(x)
424 download._rand = lambda: 10
425
426 status, done = download.next_chunk(num_retries=3)
427
428 # Check for exponential backoff using the rand function above.
429 self.assertEqual([20, 40, 80], sleeptimes)
430
Pat Ferate2b140222015-03-03 18:05:11 -0800431 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400432 self.assertEqual(False, done)
433 self.assertEqual(3, download._progress)
434 self.assertEqual(5, download._total_size)
435 self.assertEqual(3, status.resumable_progress)
436
437 # Reset time.sleep stub.
438 del sleeptimes[0:len(sleeptimes)]
439
440 status, done = download.next_chunk(num_retries=3)
441
442 # Check for exponential backoff using the rand function above.
443 self.assertEqual([20, 40, 80], sleeptimes)
444
Pat Ferate2b140222015-03-03 18:05:11 -0800445 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400446 self.assertEqual(True, done)
447 self.assertEqual(5, download._progress)
448 self.assertEqual(5, download._total_size)
449
Joe Gregorio66f57522011-11-30 11:00:00 -0500450EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
451Content-Type: application/json
452MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500453Host: www.googleapis.com
454content-length: 2\r\n\r\n{}"""
455
456
457NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
458Content-Type: application/json
459MIME-Version: 1.0
460Host: www.googleapis.com
461content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500462
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400463NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
464Content-Type: application/json
465MIME-Version: 1.0
466Host: www.googleapis.com\r\n\r\n"""
467
Joe Gregorio66f57522011-11-30 11:00:00 -0500468
469RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400470Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500471Content-Length: 14
472ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
473
474
INADA Naoki09157612015-03-25 01:51:03 +0900475BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500476Content-Type: application/http
477Content-Transfer-Encoding: binary
478Content-ID: <randomness+1>
479
480HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400481Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500482Content-Length: 14
483ETag: "etag/pony"\r\n\r\n{"foo": 42}
484
485--batch_foobarbaz
486Content-Type: application/http
487Content-Transfer-Encoding: binary
488Content-ID: <randomness+2>
489
490HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400491Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500492Content-Length: 14
493ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
494--batch_foobarbaz--"""
495
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500496
INADA Naoki09157612015-03-25 01:51:03 +0900497BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400498Content-Type: application/http
499Content-Transfer-Encoding: binary
500Content-ID: <randomness+1>
501
502HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400503Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400504Content-Length: 14
505ETag: "etag/pony"\r\n\r\n{"foo": 42}
506
507--batch_foobarbaz
508Content-Type: application/http
509Content-Transfer-Encoding: binary
510Content-ID: <randomness+2>
511
512HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400513Content-Type: application/json
514Content-Length: 245
515ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400516 "error": {
517 "errors": [
518 {
519 "domain": "usageLimits",
520 "reason": "accessNotConfigured",
521 "message": "Access Not Configured",
522 "debugInfo": "QuotaState: BLOCKED"
523 }
524 ],
525 "code": 403,
526 "message": "Access Not Configured"
527 }
528}
529
530--batch_foobarbaz--"""
531
532
INADA Naoki09157612015-03-25 01:51:03 +0900533BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500534Content-Type: application/http
535Content-Transfer-Encoding: binary
536Content-ID: <randomness+1>
537
Joe Gregorioc752e332012-07-11 14:43:52 -0400538HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400539Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500540Content-Length: 14
541ETag: "etag/pony"\r\n\r\n{"error": {"message":
542 "Authorizaton failed."}}
543
544--batch_foobarbaz
545Content-Type: application/http
546Content-Transfer-Encoding: binary
547Content-ID: <randomness+2>
548
549HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400550Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500551Content-Length: 14
552ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
553--batch_foobarbaz--"""
554
555
INADA Naoki09157612015-03-25 01:51:03 +0900556BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500557Content-Type: application/http
558Content-Transfer-Encoding: binary
559Content-ID: <randomness+1>
560
561HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400562Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500563Content-Length: 14
564ETag: "etag/pony"\r\n\r\n{"foo": 42}
565--batch_foobarbaz--"""
566
567class Callbacks(object):
568 def __init__(self):
569 self.responses = {}
570 self.exceptions = {}
571
572 def f(self, request_id, response, exception):
573 self.responses[request_id] = response
574 self.exceptions[request_id] = exception
575
576
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500577class TestHttpRequest(unittest.TestCase):
578 def test_unicode(self):
579 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
580 model = JsonModel()
581 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
582 method = u'POST'
583 request = HttpRequest(
584 http,
585 model.response,
586 uri,
587 method=method,
588 body=u'{}',
589 headers={'content-type': 'application/json'})
590 request.execute()
591 self.assertEqual(uri, http.uri)
592 self.assertEqual(str, type(http.uri))
593 self.assertEqual(method, http.method)
594 self.assertEqual(str, type(http.method))
595
Joe Gregorio9086bd32013-06-14 16:32:05 -0400596 def test_retry(self):
597 num_retries = 5
598 resp_seq = [({'status': '500'}, '')] * num_retries
599 resp_seq.append(({'status': '200'}, '{}'))
600
601 http = HttpMockSequence(resp_seq)
602 model = JsonModel()
603 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
604 method = u'POST'
605 request = HttpRequest(
606 http,
607 model.response,
608 uri,
609 method=method,
610 body=u'{}',
611 headers={'content-type': 'application/json'})
612
613 sleeptimes = []
614 request._sleep = lambda x: sleeptimes.append(x)
615 request._rand = lambda: 10
616
617 request.execute(num_retries=num_retries)
618
619 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900620 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400621 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
622
623 def test_no_retry_fails_fast(self):
624 http = HttpMockSequence([
625 ({'status': '500'}, ''),
626 ({'status': '200'}, '{}')
627 ])
628 model = JsonModel()
629 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
630 method = u'POST'
631 request = HttpRequest(
632 http,
633 model.response,
634 uri,
635 method=method,
636 body=u'{}',
637 headers={'content-type': 'application/json'})
638
639 request._rand = lambda: 1.0
640 request._sleep = lambda _: self.fail('sleep should not have been called.')
641
642 try:
643 request.execute()
644 self.fail('Should have raised an exception.')
645 except HttpError:
646 pass
647
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500648
Joe Gregorio66f57522011-11-30 11:00:00 -0500649class TestBatch(unittest.TestCase):
650
651 def setUp(self):
652 model = JsonModel()
653 self.request1 = HttpRequest(
654 None,
655 model.response,
656 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
657 method='POST',
658 body='{}',
659 headers={'content-type': 'application/json'})
660
661 self.request2 = HttpRequest(
662 None,
663 model.response,
664 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500665 method='GET',
666 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500667 headers={'content-type': 'application/json'})
668
669
670 def test_id_to_from_content_id_header(self):
671 batch = BatchHttpRequest()
672 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
673
674 def test_invalid_content_id_header(self):
675 batch = BatchHttpRequest()
676 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
677 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
678 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
679
680 def test_serialize_request(self):
681 batch = BatchHttpRequest()
682 request = HttpRequest(
683 None,
684 None,
685 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
686 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800687 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -0500688 headers={'content-type': 'application/json'},
689 methodId=None,
690 resumable=None)
691 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500692 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500693
Joe Gregoriodd813822012-01-25 10:32:47 -0500694 def test_serialize_request_media_body(self):
695 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -0800696 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -0500697 body = f.read()
698 f.close()
699
700 request = HttpRequest(
701 None,
702 None,
703 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
704 method='POST',
705 body=body,
706 headers={'content-type': 'application/json'},
707 methodId=None,
708 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500709 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -0500710 s = batch._serialize_request(request).splitlines()
711
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500712 def test_serialize_request_no_body(self):
713 batch = BatchHttpRequest()
714 request = HttpRequest(
715 None,
716 None,
717 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
718 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800719 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500720 headers={'content-type': 'application/json'},
721 methodId=None,
722 resumable=None)
723 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500724 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500725
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400726 def test_serialize_get_request_no_body(self):
727 batch = BatchHttpRequest()
728 request = HttpRequest(
729 None,
730 None,
731 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
732 method='GET',
733 body=None,
734 headers={'content-type': 'application/json'},
735 methodId=None,
736 resumable=None)
737 s = batch._serialize_request(request).splitlines()
738 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
739
Joe Gregorio66f57522011-11-30 11:00:00 -0500740 def test_deserialize_response(self):
741 batch = BatchHttpRequest()
742 resp, content = batch._deserialize_response(RESPONSE)
743
Joe Gregorio654f4a22012-02-09 14:15:44 -0500744 self.assertEqual(200, resp.status)
745 self.assertEqual('OK', resp.reason)
746 self.assertEqual(11, resp.version)
747 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -0500748
749 def test_new_id(self):
750 batch = BatchHttpRequest()
751
752 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500753 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500754
755 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500756 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500757
758 batch.add(self.request1, request_id='3')
759
760 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500761 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500762
763 def test_add(self):
764 batch = BatchHttpRequest()
765 batch.add(self.request1, request_id='1')
766 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
767
768 def test_add_fail_for_resumable(self):
769 batch = BatchHttpRequest()
770
771 upload = MediaFileUpload(
772 datafile('small.png'), chunksize=500, resumable=True)
773 self.request1.resumable = upload
774 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
775
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +0200776 def test_execute_empty_batch_no_http(self):
777 batch = BatchHttpRequest()
778 ret = batch.execute()
779 self.assertEqual(None, ret)
780
Joe Gregorio66f57522011-11-30 11:00:00 -0500781 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500782 batch = BatchHttpRequest()
783 callbacks = Callbacks()
784
785 batch.add(self.request1, callback=callbacks.f)
786 batch.add(self.request2, callback=callbacks.f)
787 http = HttpMockSequence([
788 ({'status': '200',
789 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
790 BATCH_RESPONSE),
791 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400792 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500793 self.assertEqual({'foo': 42}, callbacks.responses['1'])
794 self.assertEqual(None, callbacks.exceptions['1'])
795 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
796 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -0500797
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500798 def test_execute_request_body(self):
799 batch = BatchHttpRequest()
800
801 batch.add(self.request1)
802 batch.add(self.request2)
803 http = HttpMockSequence([
804 ({'status': '200',
805 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
806 'echo_request_body'),
807 ])
808 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400809 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500810 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +0900811 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500812 boundary, _ = e.content.split(None, 1)
813 self.assertEqual('--', boundary[:2])
814 parts = e.content.split(boundary)
815 self.assertEqual(4, len(parts))
816 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -0700817 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500818 header = parts[1].splitlines()[1]
819 self.assertEqual('Content-Type: application/http', header)
820
Joe Gregorio654f4a22012-02-09 14:15:44 -0500821 def test_execute_refresh_and_retry_on_401(self):
822 batch = BatchHttpRequest()
823 callbacks = Callbacks()
824 cred_1 = MockCredentials('Foo')
825 cred_2 = MockCredentials('Bar')
826
827 http = HttpMockSequence([
828 ({'status': '200',
829 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
830 BATCH_RESPONSE_WITH_401),
831 ({'status': '200',
832 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
833 BATCH_SINGLE_RESPONSE),
834 ])
835
836 creds_http_1 = HttpMockSequence([])
837 cred_1.authorize(creds_http_1)
838
839 creds_http_2 = HttpMockSequence([])
840 cred_2.authorize(creds_http_2)
841
842 self.request1.http = creds_http_1
843 self.request2.http = creds_http_2
844
845 batch.add(self.request1, callback=callbacks.f)
846 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400847 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500848
849 self.assertEqual({'foo': 42}, callbacks.responses['1'])
850 self.assertEqual(None, callbacks.exceptions['1'])
851 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
852 self.assertEqual(None, callbacks.exceptions['2'])
853
854 self.assertEqual(1, cred_1._refreshed)
855 self.assertEqual(0, cred_2._refreshed)
856
857 self.assertEqual(1, cred_1._authorized)
858 self.assertEqual(1, cred_2._authorized)
859
860 self.assertEqual(1, cred_2._applied)
861 self.assertEqual(2, cred_1._applied)
862
863 def test_http_errors_passed_to_callback(self):
864 batch = BatchHttpRequest()
865 callbacks = Callbacks()
866 cred_1 = MockCredentials('Foo')
867 cred_2 = MockCredentials('Bar')
868
869 http = HttpMockSequence([
870 ({'status': '200',
871 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
872 BATCH_RESPONSE_WITH_401),
873 ({'status': '200',
874 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
875 BATCH_RESPONSE_WITH_401),
876 ])
877
878 creds_http_1 = HttpMockSequence([])
879 cred_1.authorize(creds_http_1)
880
881 creds_http_2 = HttpMockSequence([])
882 cred_2.authorize(creds_http_2)
883
884 self.request1.http = creds_http_1
885 self.request2.http = creds_http_2
886
887 batch.add(self.request1, callback=callbacks.f)
888 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400889 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500890
891 self.assertEqual(None, callbacks.responses['1'])
892 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -0400893 self.assertEqual(
894 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500895 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
896 self.assertEqual(None, callbacks.exceptions['2'])
897
Joe Gregorio66f57522011-11-30 11:00:00 -0500898 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500899 callbacks = Callbacks()
900 batch = BatchHttpRequest(callback=callbacks.f)
901
902 batch.add(self.request1)
903 batch.add(self.request2)
904 http = HttpMockSequence([
905 ({'status': '200',
906 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
907 BATCH_RESPONSE),
908 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400909 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500910 self.assertEqual({'foo': 42}, callbacks.responses['1'])
911 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500912
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400913 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -0400914 callbacks = Callbacks()
915 batch = BatchHttpRequest(callback=callbacks.f)
916
917 batch.add(self.request1)
918 batch.add(self.request2)
919 http = HttpMockSequence([
920 ({'status': '200',
921 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
922 BATCH_ERROR_RESPONSE),
923 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400924 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -0400925 self.assertEqual({'foo': 42}, callbacks.responses['1'])
926 expected = ('<HttpError 403 when requesting '
927 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
928 '"Access Not Configured">')
929 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -0500930
Joe Gregorio5c120db2012-08-23 09:13:55 -0400931
Joe Gregorioba5c7902012-08-03 12:48:16 -0400932class TestRequestUriTooLong(unittest.TestCase):
933
934 def test_turn_get_into_post(self):
935
936 def _postproc(resp, content):
937 return content
938
939 http = HttpMockSequence([
940 ({'status': '200'},
941 'echo_request_body'),
942 ({'status': '200'},
943 'echo_request_headers'),
944 ])
945
946 # Send a long query parameter.
947 query = {
948 'q': 'a' * MAX_URI_LENGTH + '?&'
949 }
950 req = HttpRequest(
951 http,
952 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -0800953 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -0400954 method='GET',
955 body=None,
956 headers={},
957 methodId='foo',
958 resumable=None)
959
960 # Query parameters should be sent in the body.
961 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +0900962 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -0400963
964 # Extra headers should be set.
965 response = req.execute()
966 self.assertEqual('GET', response['x-http-method-override'])
967 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
968 self.assertEqual(
969 'application/x-www-form-urlencoded', response['content-type'])
970
Joe Gregorio5c120db2012-08-23 09:13:55 -0400971
972class TestStreamSlice(unittest.TestCase):
973 """Test _StreamSlice."""
974
975 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800976 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -0400977
978 def test_read(self):
979 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -0800980 self.assertEqual(b'', s.read(0))
981 self.assertEqual(b'0', s.read(1))
982 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -0400983
984 def test_read_too_much(self):
985 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -0800986 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -0400987
988 def test_read_all(self):
989 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -0800990 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -0400991
Ali Afshar164f37e2013-01-07 14:05:45 -0800992
993class TestResponseCallback(unittest.TestCase):
994 """Test adding callbacks to responses."""
995
996 def test_ensure_response_callback(self):
997 m = JsonModel()
998 request = HttpRequest(
999 None,
1000 m.response,
1001 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1002 method='POST',
1003 body='{}',
1004 headers={'content-type': 'application/json'})
1005 h = HttpMockSequence([ ({'status': 200}, '{}')])
1006 responses = []
1007 def _on_response(resp, responses=responses):
1008 responses.append(resp)
1009 request.add_response_callback(_on_response)
1010 request.execute(http=h)
1011 self.assertEqual(1, len(responses))
1012
1013
Craig Gurnik8e55b762015-01-20 15:00:10 -05001014class TestHttpMock(unittest.TestCase):
1015 def test_default_response_headers(self):
1016 http = HttpMock(datafile('zoo.json'))
1017 resp, content = http.request("http://example.com")
1018 self.assertEqual(resp.status, 200)
1019
Alan Briolat26b01002015-08-14 00:13:57 +01001020 def test_error_response(self):
1021 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1022 model = JsonModel()
1023 request = HttpRequest(
1024 http,
1025 model.response,
1026 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1027 method='GET',
1028 headers={})
1029 self.assertRaises(HttpError, request.execute)
1030
Craig Gurnik8e55b762015-01-20 15:00:10 -05001031
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001032if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001033 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001034 unittest.main()