blob: 82369fcc4674384250f0ba5c1b74031ff8969641 [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
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500134 def test_media_file_upload_to_from_json(self):
135 upload = MediaFileUpload(
136 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500137 self.assertEqual('image/png', upload.mimetype())
138 self.assertEqual(190, upload.size())
139 self.assertEqual(True, upload.resumable())
140 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800141 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500142
143 json = upload.to_json()
144 new_upload = MediaUpload.new_from_json(json)
145
Joe Gregorio654f4a22012-02-09 14:15:44 -0500146 self.assertEqual('image/png', new_upload.mimetype())
147 self.assertEqual(190, new_upload.size())
148 self.assertEqual(True, new_upload.resumable())
149 self.assertEqual(500, new_upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800150 self.assertEqual(b'PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500151
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400152 def test_media_file_upload_raises_on_invalid_chunksize(self):
153 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
154 datafile('small.png'), mimetype='image/png', chunksize=-2,
155 resumable=True)
156
Ali Afshar1cb6b672012-03-12 08:46:14 -0400157 def test_media_inmemory_upload(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800158 media = MediaInMemoryUpload(b'abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400159 resumable=True)
160 self.assertEqual('text/plain', media.mimetype())
161 self.assertEqual(10, media.chunksize())
162 self.assertTrue(media.resumable())
Pat Ferate2b140222015-03-03 18:05:11 -0800163 self.assertEqual(b'bc', media.getbytes(1, 2))
Ali Afshar1cb6b672012-03-12 08:46:14 -0400164 self.assertEqual(6, media.size())
165
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500166 def test_http_request_to_from_json(self):
167
168 def _postproc(*kwargs):
169 pass
170
171 http = httplib2.Http()
172 media_upload = MediaFileUpload(
173 datafile('small.png'), chunksize=500, resumable=True)
174 req = HttpRequest(
175 http,
176 _postproc,
177 'http://example.com',
178 method='POST',
179 body='{}',
180 headers={'content-type': 'multipart/related; boundary="---flubber"'},
181 methodId='foo',
182 resumable=media_upload)
183
184 json = req.to_json()
185 new_req = HttpRequest.from_json(json, http, _postproc)
186
Joe Gregorio654f4a22012-02-09 14:15:44 -0500187 self.assertEqual({'content-type':
188 'multipart/related; boundary="---flubber"'},
189 new_req.headers)
190 self.assertEqual('http://example.com', new_req.uri)
191 self.assertEqual('{}', new_req.body)
192 self.assertEqual(http, new_req.http)
193 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500194
Joe Gregorio9086bd32013-06-14 16:32:05 -0400195 self.assertEqual(random.random, new_req._rand)
196 self.assertEqual(time.sleep, new_req._sleep)
197
Joe Gregorio910b9b12012-06-12 09:36:30 -0400198
199class TestMediaIoBaseUpload(unittest.TestCase):
200
201 def test_media_io_base_upload_from_file_io(self):
Pat Ferateed9affd2015-03-03 16:03:15 -0800202 fd = FileIO(datafile('small.png'), 'r')
203 upload = MediaIoBaseUpload(
204 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
205 self.assertEqual('image/png', upload.mimetype())
206 self.assertEqual(190, upload.size())
207 self.assertEqual(True, upload.resumable())
208 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800209 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400210
211 def test_media_io_base_upload_from_file_object(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800212 f = open(datafile('small.png'), 'rb')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400213 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400214 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400215 self.assertEqual('image/png', upload.mimetype())
216 self.assertEqual(190, upload.size())
217 self.assertEqual(True, upload.resumable())
218 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800219 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400220 f.close()
221
222 def test_media_io_base_upload_serializable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800223 f = open(datafile('small.png'), 'rb')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400224 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400225
226 try:
227 json = upload.to_json()
228 self.fail('MediaIoBaseUpload should not be serializable.')
229 except NotImplementedError:
230 pass
231
Pat Feratec6050872015-03-03 18:24:59 -0800232 @unittest.skipIf(PY3, 'Strings and Bytes are different types')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400233 def test_media_io_base_upload_from_string_io(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800234 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800235 fd = StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400236 f.close()
237
238 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400239 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400240 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400241 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400242 self.assertEqual(True, upload.resumable())
243 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800244 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400245 f.close()
246
247 def test_media_io_base_upload_from_bytes(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800248 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800249 fd = BytesIO(f.read())
250 upload = MediaIoBaseUpload(
251 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
252 self.assertEqual('image/png', upload.mimetype())
253 self.assertEqual(190, upload.size())
254 self.assertEqual(True, upload.resumable())
255 self.assertEqual(500, upload.chunksize())
Pat Ferate2b140222015-03-03 18:05:11 -0800256 self.assertEqual(b'PNG', upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400257
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400258 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800259 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800260 fd = BytesIO(f.read())
261 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
262 fd, 'image/png', chunksize=-2, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400263
264 def test_media_io_base_upload_streamable(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800265 fd = BytesIO(b'stuff')
Pat Ferateed9affd2015-03-03 16:03:15 -0800266 upload = MediaIoBaseUpload(
267 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
268 self.assertEqual(True, upload.has_stream())
269 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400270
Joe Gregorio9086bd32013-06-14 16:32:05 -0400271 def test_media_io_base_next_chunk_retries(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800272 f = open(datafile('small.png'), 'rb')
Pat Ferateed9affd2015-03-03 16:03:15 -0800273 fd = BytesIO(f.read())
Joe Gregorio9086bd32013-06-14 16:32:05 -0400274 upload = MediaIoBaseUpload(
275 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
276
277 # Simulate 5XXs for both the request that creates the resumable upload and
278 # the upload itself.
279 http = HttpMockSequence([
280 ({'status': '500'}, ''),
281 ({'status': '500'}, ''),
282 ({'status': '503'}, ''),
283 ({'status': '200', 'location': 'location'}, ''),
284 ({'status': '500'}, ''),
285 ({'status': '500'}, ''),
286 ({'status': '503'}, ''),
287 ({'status': '200'}, '{}'),
288 ])
289
290 model = JsonModel()
291 uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar'
292 method = u'POST'
293 request = HttpRequest(
294 http,
295 model.response,
296 uri,
297 method=method,
298 headers={},
299 resumable=upload)
300
301 sleeptimes = []
302 request._sleep = lambda x: sleeptimes.append(x)
303 request._rand = lambda: 10
304
305 request.execute(num_retries=3)
306 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
307
Joe Gregorio910b9b12012-06-12 09:36:30 -0400308
Joe Gregorio708388c2012-06-15 13:43:04 -0400309class TestMediaIoBaseDownload(unittest.TestCase):
310
311 def setUp(self):
312 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400313 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400314 self.request = zoo.animals().get_media(name='Lion')
Pat Ferateed9affd2015-03-03 16:03:15 -0800315 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400316
317 def test_media_io_base_download(self):
318 self.request.http = HttpMockSequence([
319 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800320 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400321 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800322 'content-range': '3-4/5'}, b'45'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400323 ])
Joe Gregorio97ef1cc2013-06-13 14:47:10 -0400324 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400325
326 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400327 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400328
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400329 self.assertEqual(self.fd, download._fd)
330 self.assertEqual(3, download._chunksize)
331 self.assertEqual(0, download._progress)
332 self.assertEqual(None, download._total_size)
333 self.assertEqual(False, download._done)
334 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400335
336 status, done = download.next_chunk()
337
Pat Ferate2b140222015-03-03 18:05:11 -0800338 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400339 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400340 self.assertEqual(3, download._progress)
341 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400342 self.assertEqual(3, status.resumable_progress)
343
344 status, done = download.next_chunk()
345
Pat Ferate2b140222015-03-03 18:05:11 -0800346 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400347 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400348 self.assertEqual(5, download._progress)
349 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400350
351 def test_media_io_base_download_handle_redirects(self):
352 self.request.http = HttpMockSequence([
Joe Gregorio238feb72013-06-19 13:15:31 -0400353 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800354 'content-location': 'https://secure.example.net/lion'}, b''),
Joe Gregorio708388c2012-06-15 13:43:04 -0400355 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800356 'content-range': '0-2/5'}, b'abc'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400357 ])
358
359 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400360 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400361
362 status, done = download.next_chunk()
363
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400364 self.assertEqual('https://secure.example.net/lion', download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400365
366 def test_media_io_base_download_handle_4xx(self):
367 self.request.http = HttpMockSequence([
368 ({'status': '400'}, ''),
369 ])
370
371 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400372 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400373
374 try:
375 status, done = download.next_chunk()
376 self.fail('Should raise an exception')
377 except HttpError:
378 pass
379
380 # Even after raising an exception we can pick up where we left off.
381 self.request.http = HttpMockSequence([
382 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800383 'content-range': '0-2/5'}, b'123'),
Joe Gregorio708388c2012-06-15 13:43:04 -0400384 ])
385
386 status, done = download.next_chunk()
387
Pat Ferate2b140222015-03-03 18:05:11 -0800388 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400389
Joe Gregorio9086bd32013-06-14 16:32:05 -0400390 def test_media_io_base_download_retries_5xx(self):
391 self.request.http = HttpMockSequence([
392 ({'status': '500'}, ''),
393 ({'status': '500'}, ''),
394 ({'status': '500'}, ''),
395 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800396 'content-range': '0-2/5'}, b'123'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400397 ({'status': '503'}, ''),
398 ({'status': '503'}, ''),
399 ({'status': '503'}, ''),
400 ({'status': '200',
Pat Ferate2b140222015-03-03 18:05:11 -0800401 'content-range': '3-4/5'}, b'45'),
Joe Gregorio9086bd32013-06-14 16:32:05 -0400402 ])
403
404 download = MediaIoBaseDownload(
405 fd=self.fd, request=self.request, chunksize=3)
406
407 self.assertEqual(self.fd, download._fd)
408 self.assertEqual(3, download._chunksize)
409 self.assertEqual(0, download._progress)
410 self.assertEqual(None, download._total_size)
411 self.assertEqual(False, download._done)
412 self.assertEqual(self.request.uri, download._uri)
413
414 # Set time.sleep and random.random stubs.
415 sleeptimes = []
416 download._sleep = lambda x: sleeptimes.append(x)
417 download._rand = lambda: 10
418
419 status, done = download.next_chunk(num_retries=3)
420
421 # Check for exponential backoff using the rand function above.
422 self.assertEqual([20, 40, 80], sleeptimes)
423
Pat Ferate2b140222015-03-03 18:05:11 -0800424 self.assertEqual(self.fd.getvalue(), b'123')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400425 self.assertEqual(False, done)
426 self.assertEqual(3, download._progress)
427 self.assertEqual(5, download._total_size)
428 self.assertEqual(3, status.resumable_progress)
429
430 # Reset time.sleep stub.
431 del sleeptimes[0:len(sleeptimes)]
432
433 status, done = download.next_chunk(num_retries=3)
434
435 # Check for exponential backoff using the rand function above.
436 self.assertEqual([20, 40, 80], sleeptimes)
437
Pat Ferate2b140222015-03-03 18:05:11 -0800438 self.assertEqual(self.fd.getvalue(), b'12345')
Joe Gregorio9086bd32013-06-14 16:32:05 -0400439 self.assertEqual(True, done)
440 self.assertEqual(5, download._progress)
441 self.assertEqual(5, download._total_size)
442
Joe Gregorio66f57522011-11-30 11:00:00 -0500443EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
444Content-Type: application/json
445MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500446Host: www.googleapis.com
447content-length: 2\r\n\r\n{}"""
448
449
450NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
451Content-Type: application/json
452MIME-Version: 1.0
453Host: www.googleapis.com
454content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500455
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400456NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
457Content-Type: application/json
458MIME-Version: 1.0
459Host: www.googleapis.com\r\n\r\n"""
460
Joe Gregorio66f57522011-11-30 11:00:00 -0500461
462RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400463Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500464Content-Length: 14
465ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
466
467
INADA Naoki09157612015-03-25 01:51:03 +0900468BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500469Content-Type: application/http
470Content-Transfer-Encoding: binary
471Content-ID: <randomness+1>
472
473HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400474Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500475Content-Length: 14
476ETag: "etag/pony"\r\n\r\n{"foo": 42}
477
478--batch_foobarbaz
479Content-Type: application/http
480Content-Transfer-Encoding: binary
481Content-ID: <randomness+2>
482
483HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400484Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500485Content-Length: 14
486ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
487--batch_foobarbaz--"""
488
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500489
INADA Naoki09157612015-03-25 01:51:03 +0900490BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400491Content-Type: application/http
492Content-Transfer-Encoding: binary
493Content-ID: <randomness+1>
494
495HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400496Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400497Content-Length: 14
498ETag: "etag/pony"\r\n\r\n{"foo": 42}
499
500--batch_foobarbaz
501Content-Type: application/http
502Content-Transfer-Encoding: binary
503Content-ID: <randomness+2>
504
505HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400506Content-Type: application/json
507Content-Length: 245
508ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400509 "error": {
510 "errors": [
511 {
512 "domain": "usageLimits",
513 "reason": "accessNotConfigured",
514 "message": "Access Not Configured",
515 "debugInfo": "QuotaState: BLOCKED"
516 }
517 ],
518 "code": 403,
519 "message": "Access Not Configured"
520 }
521}
522
523--batch_foobarbaz--"""
524
525
INADA Naoki09157612015-03-25 01:51:03 +0900526BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500527Content-Type: application/http
528Content-Transfer-Encoding: binary
529Content-ID: <randomness+1>
530
Joe Gregorioc752e332012-07-11 14:43:52 -0400531HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400532Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500533Content-Length: 14
534ETag: "etag/pony"\r\n\r\n{"error": {"message":
535 "Authorizaton failed."}}
536
537--batch_foobarbaz
538Content-Type: application/http
539Content-Transfer-Encoding: binary
540Content-ID: <randomness+2>
541
542HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400543Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500544Content-Length: 14
545ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
546--batch_foobarbaz--"""
547
548
INADA Naoki09157612015-03-25 01:51:03 +0900549BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500550Content-Type: application/http
551Content-Transfer-Encoding: binary
552Content-ID: <randomness+1>
553
554HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400555Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500556Content-Length: 14
557ETag: "etag/pony"\r\n\r\n{"foo": 42}
558--batch_foobarbaz--"""
559
560class Callbacks(object):
561 def __init__(self):
562 self.responses = {}
563 self.exceptions = {}
564
565 def f(self, request_id, response, exception):
566 self.responses[request_id] = response
567 self.exceptions[request_id] = exception
568
569
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500570class TestHttpRequest(unittest.TestCase):
571 def test_unicode(self):
572 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
573 model = JsonModel()
574 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
575 method = u'POST'
576 request = HttpRequest(
577 http,
578 model.response,
579 uri,
580 method=method,
581 body=u'{}',
582 headers={'content-type': 'application/json'})
583 request.execute()
584 self.assertEqual(uri, http.uri)
585 self.assertEqual(str, type(http.uri))
586 self.assertEqual(method, http.method)
587 self.assertEqual(str, type(http.method))
588
Joe Gregorio9086bd32013-06-14 16:32:05 -0400589 def test_retry(self):
590 num_retries = 5
591 resp_seq = [({'status': '500'}, '')] * num_retries
592 resp_seq.append(({'status': '200'}, '{}'))
593
594 http = HttpMockSequence(resp_seq)
595 model = JsonModel()
596 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
597 method = u'POST'
598 request = HttpRequest(
599 http,
600 model.response,
601 uri,
602 method=method,
603 body=u'{}',
604 headers={'content-type': 'application/json'})
605
606 sleeptimes = []
607 request._sleep = lambda x: sleeptimes.append(x)
608 request._rand = lambda: 10
609
610 request.execute(num_retries=num_retries)
611
612 self.assertEqual(num_retries, len(sleeptimes))
INADA Naokid898a372015-03-04 03:52:46 +0900613 for retry_num in range(num_retries):
Joe Gregorio9086bd32013-06-14 16:32:05 -0400614 self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
615
616 def test_no_retry_fails_fast(self):
617 http = HttpMockSequence([
618 ({'status': '500'}, ''),
619 ({'status': '200'}, '{}')
620 ])
621 model = JsonModel()
622 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
623 method = u'POST'
624 request = HttpRequest(
625 http,
626 model.response,
627 uri,
628 method=method,
629 body=u'{}',
630 headers={'content-type': 'application/json'})
631
632 request._rand = lambda: 1.0
633 request._sleep = lambda _: self.fail('sleep should not have been called.')
634
635 try:
636 request.execute()
637 self.fail('Should have raised an exception.')
638 except HttpError:
639 pass
640
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500641
Joe Gregorio66f57522011-11-30 11:00:00 -0500642class TestBatch(unittest.TestCase):
643
644 def setUp(self):
645 model = JsonModel()
646 self.request1 = HttpRequest(
647 None,
648 model.response,
649 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
650 method='POST',
651 body='{}',
652 headers={'content-type': 'application/json'})
653
654 self.request2 = HttpRequest(
655 None,
656 model.response,
657 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500658 method='GET',
659 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500660 headers={'content-type': 'application/json'})
661
662
663 def test_id_to_from_content_id_header(self):
664 batch = BatchHttpRequest()
665 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
666
667 def test_invalid_content_id_header(self):
668 batch = BatchHttpRequest()
669 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
670 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
671 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
672
673 def test_serialize_request(self):
674 batch = BatchHttpRequest()
675 request = HttpRequest(
676 None,
677 None,
678 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
679 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800680 body=u'{}',
Joe Gregorio66f57522011-11-30 11:00:00 -0500681 headers={'content-type': 'application/json'},
682 methodId=None,
683 resumable=None)
684 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500685 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500686
Joe Gregoriodd813822012-01-25 10:32:47 -0500687 def test_serialize_request_media_body(self):
688 batch = BatchHttpRequest()
Pat Ferate2b140222015-03-03 18:05:11 -0800689 f = open(datafile('small.png'), 'rb')
Joe Gregoriodd813822012-01-25 10:32:47 -0500690 body = f.read()
691 f.close()
692
693 request = HttpRequest(
694 None,
695 None,
696 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
697 method='POST',
698 body=body,
699 headers={'content-type': 'application/json'},
700 methodId=None,
701 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500702 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -0500703 s = batch._serialize_request(request).splitlines()
704
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500705 def test_serialize_request_no_body(self):
706 batch = BatchHttpRequest()
707 request = HttpRequest(
708 None,
709 None,
710 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
711 method='POST',
Pat Ferate2b140222015-03-03 18:05:11 -0800712 body=b'',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500713 headers={'content-type': 'application/json'},
714 methodId=None,
715 resumable=None)
716 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500717 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500718
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400719 def test_serialize_get_request_no_body(self):
720 batch = BatchHttpRequest()
721 request = HttpRequest(
722 None,
723 None,
724 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
725 method='GET',
726 body=None,
727 headers={'content-type': 'application/json'},
728 methodId=None,
729 resumable=None)
730 s = batch._serialize_request(request).splitlines()
731 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
732
Joe Gregorio66f57522011-11-30 11:00:00 -0500733 def test_deserialize_response(self):
734 batch = BatchHttpRequest()
735 resp, content = batch._deserialize_response(RESPONSE)
736
Joe Gregorio654f4a22012-02-09 14:15:44 -0500737 self.assertEqual(200, resp.status)
738 self.assertEqual('OK', resp.reason)
739 self.assertEqual(11, resp.version)
740 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -0500741
742 def test_new_id(self):
743 batch = BatchHttpRequest()
744
745 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500746 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500747
748 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500749 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500750
751 batch.add(self.request1, request_id='3')
752
753 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500754 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500755
756 def test_add(self):
757 batch = BatchHttpRequest()
758 batch.add(self.request1, request_id='1')
759 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
760
761 def test_add_fail_for_resumable(self):
762 batch = BatchHttpRequest()
763
764 upload = MediaFileUpload(
765 datafile('small.png'), chunksize=500, resumable=True)
766 self.request1.resumable = upload
767 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
768
Mohamed Zenadi1b5350d2015-07-30 11:52:39 +0200769 def test_execute_empty_batch_no_http(self):
770 batch = BatchHttpRequest()
771 ret = batch.execute()
772 self.assertEqual(None, ret)
773
Joe Gregorio66f57522011-11-30 11:00:00 -0500774 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500775 batch = BatchHttpRequest()
776 callbacks = Callbacks()
777
778 batch.add(self.request1, callback=callbacks.f)
779 batch.add(self.request2, callback=callbacks.f)
780 http = HttpMockSequence([
781 ({'status': '200',
782 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
783 BATCH_RESPONSE),
784 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400785 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500786 self.assertEqual({'foo': 42}, callbacks.responses['1'])
787 self.assertEqual(None, callbacks.exceptions['1'])
788 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
789 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -0500790
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500791 def test_execute_request_body(self):
792 batch = BatchHttpRequest()
793
794 batch.add(self.request1)
795 batch.add(self.request2)
796 http = HttpMockSequence([
797 ({'status': '200',
798 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
799 'echo_request_body'),
800 ])
801 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400802 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500803 self.fail('Should raise exception')
INADA Naokic1505df2014-08-20 15:19:53 +0900804 except BatchError as e:
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500805 boundary, _ = e.content.split(None, 1)
806 self.assertEqual('--', boundary[:2])
807 parts = e.content.split(boundary)
808 self.assertEqual(4, len(parts))
809 self.assertEqual('', parts[0])
Craig Citro4282aa32014-06-21 00:33:39 -0700810 self.assertEqual('--', parts[3].rstrip())
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500811 header = parts[1].splitlines()[1]
812 self.assertEqual('Content-Type: application/http', header)
813
Joe Gregorio654f4a22012-02-09 14:15:44 -0500814 def test_execute_refresh_and_retry_on_401(self):
815 batch = BatchHttpRequest()
816 callbacks = Callbacks()
817 cred_1 = MockCredentials('Foo')
818 cred_2 = MockCredentials('Bar')
819
820 http = HttpMockSequence([
821 ({'status': '200',
822 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
823 BATCH_RESPONSE_WITH_401),
824 ({'status': '200',
825 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
826 BATCH_SINGLE_RESPONSE),
827 ])
828
829 creds_http_1 = HttpMockSequence([])
830 cred_1.authorize(creds_http_1)
831
832 creds_http_2 = HttpMockSequence([])
833 cred_2.authorize(creds_http_2)
834
835 self.request1.http = creds_http_1
836 self.request2.http = creds_http_2
837
838 batch.add(self.request1, callback=callbacks.f)
839 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400840 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500841
842 self.assertEqual({'foo': 42}, callbacks.responses['1'])
843 self.assertEqual(None, callbacks.exceptions['1'])
844 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
845 self.assertEqual(None, callbacks.exceptions['2'])
846
847 self.assertEqual(1, cred_1._refreshed)
848 self.assertEqual(0, cred_2._refreshed)
849
850 self.assertEqual(1, cred_1._authorized)
851 self.assertEqual(1, cred_2._authorized)
852
853 self.assertEqual(1, cred_2._applied)
854 self.assertEqual(2, cred_1._applied)
855
856 def test_http_errors_passed_to_callback(self):
857 batch = BatchHttpRequest()
858 callbacks = Callbacks()
859 cred_1 = MockCredentials('Foo')
860 cred_2 = MockCredentials('Bar')
861
862 http = HttpMockSequence([
863 ({'status': '200',
864 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
865 BATCH_RESPONSE_WITH_401),
866 ({'status': '200',
867 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
868 BATCH_RESPONSE_WITH_401),
869 ])
870
871 creds_http_1 = HttpMockSequence([])
872 cred_1.authorize(creds_http_1)
873
874 creds_http_2 = HttpMockSequence([])
875 cred_2.authorize(creds_http_2)
876
877 self.request1.http = creds_http_1
878 self.request2.http = creds_http_2
879
880 batch.add(self.request1, callback=callbacks.f)
881 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400882 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500883
884 self.assertEqual(None, callbacks.responses['1'])
885 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -0400886 self.assertEqual(
887 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500888 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
889 self.assertEqual(None, callbacks.exceptions['2'])
890
Joe Gregorio66f57522011-11-30 11:00:00 -0500891 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500892 callbacks = Callbacks()
893 batch = BatchHttpRequest(callback=callbacks.f)
894
895 batch.add(self.request1)
896 batch.add(self.request2)
897 http = HttpMockSequence([
898 ({'status': '200',
899 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
900 BATCH_RESPONSE),
901 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400902 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500903 self.assertEqual({'foo': 42}, callbacks.responses['1'])
904 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500905
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400906 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -0400907 callbacks = Callbacks()
908 batch = BatchHttpRequest(callback=callbacks.f)
909
910 batch.add(self.request1)
911 batch.add(self.request2)
912 http = HttpMockSequence([
913 ({'status': '200',
914 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
915 BATCH_ERROR_RESPONSE),
916 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400917 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -0400918 self.assertEqual({'foo': 42}, callbacks.responses['1'])
919 expected = ('<HttpError 403 when requesting '
920 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
921 '"Access Not Configured">')
922 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -0500923
Joe Gregorio5c120db2012-08-23 09:13:55 -0400924
Joe Gregorioba5c7902012-08-03 12:48:16 -0400925class TestRequestUriTooLong(unittest.TestCase):
926
927 def test_turn_get_into_post(self):
928
929 def _postproc(resp, content):
930 return content
931
932 http = HttpMockSequence([
933 ({'status': '200'},
934 'echo_request_body'),
935 ({'status': '200'},
936 'echo_request_headers'),
937 ])
938
939 # Send a long query parameter.
940 query = {
941 'q': 'a' * MAX_URI_LENGTH + '?&'
942 }
943 req = HttpRequest(
944 http,
945 _postproc,
Pat Ferated5b61bd2015-03-03 16:04:11 -0800946 'http://example.com?' + urlencode(query),
Joe Gregorioba5c7902012-08-03 12:48:16 -0400947 method='GET',
948 body=None,
949 headers={},
950 methodId='foo',
951 resumable=None)
952
953 # Query parameters should be sent in the body.
954 response = req.execute()
INADA Naoki09157612015-03-25 01:51:03 +0900955 self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response)
Joe Gregorioba5c7902012-08-03 12:48:16 -0400956
957 # Extra headers should be set.
958 response = req.execute()
959 self.assertEqual('GET', response['x-http-method-override'])
960 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
961 self.assertEqual(
962 'application/x-www-form-urlencoded', response['content-type'])
963
Joe Gregorio5c120db2012-08-23 09:13:55 -0400964
965class TestStreamSlice(unittest.TestCase):
966 """Test _StreamSlice."""
967
968 def setUp(self):
Pat Ferate2b140222015-03-03 18:05:11 -0800969 self.stream = BytesIO(b'0123456789')
Joe Gregorio5c120db2012-08-23 09:13:55 -0400970
971 def test_read(self):
972 s = _StreamSlice(self.stream, 0, 4)
Pat Ferate2b140222015-03-03 18:05:11 -0800973 self.assertEqual(b'', s.read(0))
974 self.assertEqual(b'0', s.read(1))
975 self.assertEqual(b'123', s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -0400976
977 def test_read_too_much(self):
978 s = _StreamSlice(self.stream, 1, 4)
Pat Ferate2b140222015-03-03 18:05:11 -0800979 self.assertEqual(b'1234', s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -0400980
981 def test_read_all(self):
982 s = _StreamSlice(self.stream, 2, 1)
Pat Ferate2b140222015-03-03 18:05:11 -0800983 self.assertEqual(b'2', s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -0400984
Ali Afshar164f37e2013-01-07 14:05:45 -0800985
986class TestResponseCallback(unittest.TestCase):
987 """Test adding callbacks to responses."""
988
989 def test_ensure_response_callback(self):
990 m = JsonModel()
991 request = HttpRequest(
992 None,
993 m.response,
994 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
995 method='POST',
996 body='{}',
997 headers={'content-type': 'application/json'})
998 h = HttpMockSequence([ ({'status': 200}, '{}')])
999 responses = []
1000 def _on_response(resp, responses=responses):
1001 responses.append(resp)
1002 request.add_response_callback(_on_response)
1003 request.execute(http=h)
1004 self.assertEqual(1, len(responses))
1005
1006
Craig Gurnik8e55b762015-01-20 15:00:10 -05001007class TestHttpMock(unittest.TestCase):
1008 def test_default_response_headers(self):
1009 http = HttpMock(datafile('zoo.json'))
1010 resp, content = http.request("http://example.com")
1011 self.assertEqual(resp.status, 200)
1012
Alan Briolat26b01002015-08-14 00:13:57 +01001013 def test_error_response(self):
1014 http = HttpMock(datafile('bad_request.json'), {'status': '400'})
1015 model = JsonModel()
1016 request = HttpRequest(
1017 http,
1018 model.response,
1019 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
1020 method='GET',
1021 headers={})
1022 self.assertRaises(HttpError, request.execute)
1023
Craig Gurnik8e55b762015-01-20 15:00:10 -05001024
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001025if __name__ == '__main__':
Joe Gregorio9086bd32013-06-14 16:32:05 -04001026 logging.getLogger().setLevel(logging.ERROR)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001027 unittest.main()