blob: 7fc9c5168969e9be6520ba2aaa72f00bb30f1dc6 [file] [log] [blame]
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05001#!/usr/bin/python2.4
2#
3# Copyright 2010 Google Inc.
4#
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
19Unit tests for the apiclient.http.
20"""
21
22__author__ = 'jcgregorio@google.com (Joe Gregorio)'
23
Joe Gregorio7cbceab2011-06-27 10:46:54 -040024# Do not remove the httplib2 import
25import httplib2
Joe Gregoriod0bd3882011-11-22 09:49:47 -050026import os
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050027import unittest
Joe Gregorioba5c7902012-08-03 12:48:16 -040028import urllib
Joe Gregorio910b9b12012-06-12 09:36:30 -040029import StringIO
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050030
Joe Gregorio708388c2012-06-15 13:43:04 -040031from apiclient.discovery import build
Joe Gregorio66f57522011-11-30 11:00:00 -050032from apiclient.errors import BatchError
Joe Gregorio708388c2012-06-15 13:43:04 -040033from apiclient.errors import HttpError
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040034from apiclient.errors import InvalidChunkSizeError
Joe Gregorio66f57522011-11-30 11:00:00 -050035from apiclient.http import BatchHttpRequest
Joe Gregorio708388c2012-06-15 13:43:04 -040036from apiclient.http import HttpMock
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050037from apiclient.http import HttpMockSequence
Joe Gregoriod0bd3882011-11-22 09:49:47 -050038from apiclient.http import HttpRequest
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040039from apiclient.http import MAX_URI_LENGTH
Joe Gregorio5c120db2012-08-23 09:13:55 -040040from apiclient.http import MediaFileUpload
41from apiclient.http import MediaInMemoryUpload
42from apiclient.http import MediaIoBaseDownload
43from apiclient.http import MediaIoBaseUpload
44from apiclient.http import MediaUpload
45from apiclient.http import _StreamSlice
46from apiclient.http import set_user_agent
Joe Gregorio66f57522011-11-30 11:00:00 -050047from apiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050048from oauth2client.client import Credentials
49
50
51class MockCredentials(Credentials):
52 """Mock class for all Credentials objects."""
53 def __init__(self, bearer_token):
54 super(MockCredentials, self).__init__()
55 self._authorized = 0
56 self._refreshed = 0
57 self._applied = 0
58 self._bearer_token = bearer_token
59
60 def authorize(self, http):
61 self._authorized += 1
62
63 request_orig = http.request
64
65 # The closure that will replace 'httplib2.Http.request'.
66 def new_request(uri, method='GET', body=None, headers=None,
67 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
68 connection_type=None):
69 # Modify the request headers to add the appropriate
70 # Authorization header.
71 if headers is None:
72 headers = {}
73 self.apply(headers)
74
75 resp, content = request_orig(uri, method, body, headers,
76 redirections, connection_type)
77
78 return resp, content
79
80 # Replace the request method with our own closure.
81 http.request = new_request
82
83 # Set credentials as a property of the request method.
84 setattr(http.request, 'credentials', self)
85
86 return http
87
88 def refresh(self, http):
89 self._refreshed += 1
90
91 def apply(self, headers):
92 self._applied += 1
93 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050094
95
Joe Gregoriod0bd3882011-11-22 09:49:47 -050096DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
97
98
99def datafile(filename):
100 return os.path.join(DATA_DIR, filename)
101
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500102class TestUserAgent(unittest.TestCase):
103
104 def test_set_user_agent(self):
105 http = HttpMockSequence([
106 ({'status': '200'}, 'echo_request_headers'),
107 ])
108
109 http = set_user_agent(http, "my_app/5.5")
110 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500111 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500112
113 def test_set_user_agent_nested(self):
114 http = HttpMockSequence([
115 ({'status': '200'}, 'echo_request_headers'),
116 ])
117
118 http = set_user_agent(http, "my_app/5.5")
119 http = set_user_agent(http, "my_library/0.1")
120 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500121 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500122
Joe Gregorio910b9b12012-06-12 09:36:30 -0400123
124class TestMediaUpload(unittest.TestCase):
125
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500126 def test_media_file_upload_to_from_json(self):
127 upload = MediaFileUpload(
128 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500129 self.assertEqual('image/png', upload.mimetype())
130 self.assertEqual(190, upload.size())
131 self.assertEqual(True, upload.resumable())
132 self.assertEqual(500, upload.chunksize())
133 self.assertEqual('PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500134
135 json = upload.to_json()
136 new_upload = MediaUpload.new_from_json(json)
137
Joe Gregorio654f4a22012-02-09 14:15:44 -0500138 self.assertEqual('image/png', new_upload.mimetype())
139 self.assertEqual(190, new_upload.size())
140 self.assertEqual(True, new_upload.resumable())
141 self.assertEqual(500, new_upload.chunksize())
142 self.assertEqual('PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500143
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400144 def test_media_file_upload_raises_on_invalid_chunksize(self):
145 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
146 datafile('small.png'), mimetype='image/png', chunksize=-2,
147 resumable=True)
148
Ali Afshar1cb6b672012-03-12 08:46:14 -0400149 def test_media_inmemory_upload(self):
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400150 media = MediaInMemoryUpload('abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400151 resumable=True)
152 self.assertEqual('text/plain', media.mimetype())
153 self.assertEqual(10, media.chunksize())
154 self.assertTrue(media.resumable())
155 self.assertEqual('bc', media.getbytes(1, 2))
156 self.assertEqual(6, media.size())
157
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500158 def test_http_request_to_from_json(self):
159
160 def _postproc(*kwargs):
161 pass
162
163 http = httplib2.Http()
164 media_upload = MediaFileUpload(
165 datafile('small.png'), chunksize=500, resumable=True)
166 req = HttpRequest(
167 http,
168 _postproc,
169 'http://example.com',
170 method='POST',
171 body='{}',
172 headers={'content-type': 'multipart/related; boundary="---flubber"'},
173 methodId='foo',
174 resumable=media_upload)
175
176 json = req.to_json()
177 new_req = HttpRequest.from_json(json, http, _postproc)
178
Joe Gregorio654f4a22012-02-09 14:15:44 -0500179 self.assertEqual({'content-type':
180 'multipart/related; boundary="---flubber"'},
181 new_req.headers)
182 self.assertEqual('http://example.com', new_req.uri)
183 self.assertEqual('{}', new_req.body)
184 self.assertEqual(http, new_req.http)
185 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500186
Joe Gregorio910b9b12012-06-12 09:36:30 -0400187
188class TestMediaIoBaseUpload(unittest.TestCase):
189
190 def test_media_io_base_upload_from_file_io(self):
191 try:
192 import io
193
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400194 fd = io.FileIO(datafile('small.png'), 'r')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400195 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400196 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400197 self.assertEqual('image/png', upload.mimetype())
198 self.assertEqual(190, upload.size())
199 self.assertEqual(True, upload.resumable())
200 self.assertEqual(500, upload.chunksize())
201 self.assertEqual('PNG', upload.getbytes(1, 3))
202 except ImportError:
203 pass
204
205 def test_media_io_base_upload_from_file_object(self):
206 f = open(datafile('small.png'), 'r')
207 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400208 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400209 self.assertEqual('image/png', upload.mimetype())
210 self.assertEqual(190, upload.size())
211 self.assertEqual(True, upload.resumable())
212 self.assertEqual(500, upload.chunksize())
213 self.assertEqual('PNG', upload.getbytes(1, 3))
214 f.close()
215
216 def test_media_io_base_upload_serializable(self):
217 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400218 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400219
220 try:
221 json = upload.to_json()
222 self.fail('MediaIoBaseUpload should not be serializable.')
223 except NotImplementedError:
224 pass
225
226 def test_media_io_base_upload_from_string_io(self):
227 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400228 fd = StringIO.StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400229 f.close()
230
231 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400232 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400233 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400234 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400235 self.assertEqual(True, upload.resumable())
236 self.assertEqual(500, upload.chunksize())
237 self.assertEqual('PNG', upload.getbytes(1, 3))
238 f.close()
239
240 def test_media_io_base_upload_from_bytes(self):
241 try:
242 import io
243
244 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400245 fd = io.BytesIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400246 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400247 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400248 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400249 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400250 self.assertEqual(True, upload.resumable())
251 self.assertEqual(500, upload.chunksize())
252 self.assertEqual('PNG', upload.getbytes(1, 3))
253 except ImportError:
254 pass
255
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400256 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
257 try:
258 import io
259
260 f = open(datafile('small.png'), 'r')
261 fd = io.BytesIO(f.read())
262 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
263 fd, 'image/png', chunksize=-2, resumable=True)
264 except ImportError:
265 pass
266
267 def test_media_io_base_upload_streamable(self):
268 try:
269 import io
270
271 fd = io.BytesIO('stuff')
272 upload = MediaIoBaseUpload(
273 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
274 self.assertEqual(True, upload.has_stream())
275 self.assertEqual(fd, upload.stream())
276 except ImportError:
277 pass
278
Joe Gregorio910b9b12012-06-12 09:36:30 -0400279
Joe Gregorio708388c2012-06-15 13:43:04 -0400280class TestMediaIoBaseDownload(unittest.TestCase):
281
282 def setUp(self):
283 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400284 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400285 self.request = zoo.animals().get_media(name='Lion')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400286 self.fd = StringIO.StringIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400287
288 def test_media_io_base_download(self):
289 self.request.http = HttpMockSequence([
290 ({'status': '200',
291 'content-range': '0-2/5'}, '123'),
292 ({'status': '200',
293 'content-range': '3-4/5'}, '45'),
294 ])
295
296 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400297 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400298
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400299 self.assertEqual(self.fd, download._fd)
300 self.assertEqual(3, download._chunksize)
301 self.assertEqual(0, download._progress)
302 self.assertEqual(None, download._total_size)
303 self.assertEqual(False, download._done)
304 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400305
306 status, done = download.next_chunk()
307
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400308 self.assertEqual(self.fd.getvalue(), '123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400309 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400310 self.assertEqual(3, download._progress)
311 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400312 self.assertEqual(3, status.resumable_progress)
313
314 status, done = download.next_chunk()
315
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400316 self.assertEqual(self.fd.getvalue(), '12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400317 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400318 self.assertEqual(5, download._progress)
319 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400320
321 def test_media_io_base_download_handle_redirects(self):
322 self.request.http = HttpMockSequence([
323 ({'status': '307',
324 'location': 'https://secure.example.net/lion'}, ''),
325 ({'status': '200',
326 'content-range': '0-2/5'}, 'abc'),
327 ])
328
329 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400330 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400331
332 status, done = download.next_chunk()
333
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400334 self.assertEqual('https://secure.example.net/lion', download._uri)
335 self.assertEqual(self.fd.getvalue(), 'abc')
Joe Gregorio708388c2012-06-15 13:43:04 -0400336 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400337 self.assertEqual(3, download._progress)
338 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400339
340 def test_media_io_base_download_handle_4xx(self):
341 self.request.http = HttpMockSequence([
342 ({'status': '400'}, ''),
343 ])
344
345 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400346 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400347
348 try:
349 status, done = download.next_chunk()
350 self.fail('Should raise an exception')
351 except HttpError:
352 pass
353
354 # Even after raising an exception we can pick up where we left off.
355 self.request.http = HttpMockSequence([
356 ({'status': '200',
357 'content-range': '0-2/5'}, '123'),
358 ])
359
360 status, done = download.next_chunk()
361
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400362 self.assertEqual(self.fd.getvalue(), '123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400363
Joe Gregorio66f57522011-11-30 11:00:00 -0500364EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
365Content-Type: application/json
366MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500367Host: www.googleapis.com
368content-length: 2\r\n\r\n{}"""
369
370
371NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
372Content-Type: application/json
373MIME-Version: 1.0
374Host: www.googleapis.com
375content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500376
377
378RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400379Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500380Content-Length: 14
381ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
382
383
384BATCH_RESPONSE = """--batch_foobarbaz
385Content-Type: application/http
386Content-Transfer-Encoding: binary
387Content-ID: <randomness+1>
388
389HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400390Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500391Content-Length: 14
392ETag: "etag/pony"\r\n\r\n{"foo": 42}
393
394--batch_foobarbaz
395Content-Type: application/http
396Content-Transfer-Encoding: binary
397Content-ID: <randomness+2>
398
399HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400400Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500401Content-Length: 14
402ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
403--batch_foobarbaz--"""
404
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500405
Joe Gregorio3fb93672012-07-25 11:31:11 -0400406BATCH_ERROR_RESPONSE = """--batch_foobarbaz
407Content-Type: application/http
408Content-Transfer-Encoding: binary
409Content-ID: <randomness+1>
410
411HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400412Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400413Content-Length: 14
414ETag: "etag/pony"\r\n\r\n{"foo": 42}
415
416--batch_foobarbaz
417Content-Type: application/http
418Content-Transfer-Encoding: binary
419Content-ID: <randomness+2>
420
421HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400422Content-Type: application/json
423Content-Length: 245
424ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400425 "error": {
426 "errors": [
427 {
428 "domain": "usageLimits",
429 "reason": "accessNotConfigured",
430 "message": "Access Not Configured",
431 "debugInfo": "QuotaState: BLOCKED"
432 }
433 ],
434 "code": 403,
435 "message": "Access Not Configured"
436 }
437}
438
439--batch_foobarbaz--"""
440
441
Joe Gregorio654f4a22012-02-09 14:15:44 -0500442BATCH_RESPONSE_WITH_401 = """--batch_foobarbaz
443Content-Type: application/http
444Content-Transfer-Encoding: binary
445Content-ID: <randomness+1>
446
Joe Gregorioc752e332012-07-11 14:43:52 -0400447HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400448Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500449Content-Length: 14
450ETag: "etag/pony"\r\n\r\n{"error": {"message":
451 "Authorizaton failed."}}
452
453--batch_foobarbaz
454Content-Type: application/http
455Content-Transfer-Encoding: binary
456Content-ID: <randomness+2>
457
458HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400459Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500460Content-Length: 14
461ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
462--batch_foobarbaz--"""
463
464
465BATCH_SINGLE_RESPONSE = """--batch_foobarbaz
466Content-Type: application/http
467Content-Transfer-Encoding: binary
468Content-ID: <randomness+1>
469
470HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400471Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500472Content-Length: 14
473ETag: "etag/pony"\r\n\r\n{"foo": 42}
474--batch_foobarbaz--"""
475
476class Callbacks(object):
477 def __init__(self):
478 self.responses = {}
479 self.exceptions = {}
480
481 def f(self, request_id, response, exception):
482 self.responses[request_id] = response
483 self.exceptions[request_id] = exception
484
485
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500486class TestHttpRequest(unittest.TestCase):
487 def test_unicode(self):
488 http = HttpMock(datafile('zoo.json'), headers={'status': '200'})
489 model = JsonModel()
490 uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar'
491 method = u'POST'
492 request = HttpRequest(
493 http,
494 model.response,
495 uri,
496 method=method,
497 body=u'{}',
498 headers={'content-type': 'application/json'})
499 request.execute()
500 self.assertEqual(uri, http.uri)
501 self.assertEqual(str, type(http.uri))
502 self.assertEqual(method, http.method)
503 self.assertEqual(str, type(http.method))
504
505
Joe Gregorio66f57522011-11-30 11:00:00 -0500506class TestBatch(unittest.TestCase):
507
508 def setUp(self):
509 model = JsonModel()
510 self.request1 = HttpRequest(
511 None,
512 model.response,
513 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
514 method='POST',
515 body='{}',
516 headers={'content-type': 'application/json'})
517
518 self.request2 = HttpRequest(
519 None,
520 model.response,
521 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500522 method='GET',
523 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500524 headers={'content-type': 'application/json'})
525
526
527 def test_id_to_from_content_id_header(self):
528 batch = BatchHttpRequest()
529 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
530
531 def test_invalid_content_id_header(self):
532 batch = BatchHttpRequest()
533 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
534 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
535 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
536
537 def test_serialize_request(self):
538 batch = BatchHttpRequest()
539 request = HttpRequest(
540 None,
541 None,
542 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
543 method='POST',
544 body='{}',
545 headers={'content-type': 'application/json'},
546 methodId=None,
547 resumable=None)
548 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500549 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500550
Joe Gregoriodd813822012-01-25 10:32:47 -0500551 def test_serialize_request_media_body(self):
552 batch = BatchHttpRequest()
553 f = open(datafile('small.png'))
554 body = f.read()
555 f.close()
556
557 request = HttpRequest(
558 None,
559 None,
560 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
561 method='POST',
562 body=body,
563 headers={'content-type': 'application/json'},
564 methodId=None,
565 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500566 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -0500567 s = batch._serialize_request(request).splitlines()
568
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500569 def test_serialize_request_no_body(self):
570 batch = BatchHttpRequest()
571 request = HttpRequest(
572 None,
573 None,
574 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
575 method='POST',
576 body='',
577 headers={'content-type': 'application/json'},
578 methodId=None,
579 resumable=None)
580 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500581 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500582
Joe Gregorio66f57522011-11-30 11:00:00 -0500583 def test_deserialize_response(self):
584 batch = BatchHttpRequest()
585 resp, content = batch._deserialize_response(RESPONSE)
586
Joe Gregorio654f4a22012-02-09 14:15:44 -0500587 self.assertEqual(200, resp.status)
588 self.assertEqual('OK', resp.reason)
589 self.assertEqual(11, resp.version)
590 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -0500591
592 def test_new_id(self):
593 batch = BatchHttpRequest()
594
595 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500596 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500597
598 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500599 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500600
601 batch.add(self.request1, request_id='3')
602
603 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500604 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500605
606 def test_add(self):
607 batch = BatchHttpRequest()
608 batch.add(self.request1, request_id='1')
609 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
610
611 def test_add_fail_for_resumable(self):
612 batch = BatchHttpRequest()
613
614 upload = MediaFileUpload(
615 datafile('small.png'), chunksize=500, resumable=True)
616 self.request1.resumable = upload
617 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
618
619 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500620 batch = BatchHttpRequest()
621 callbacks = Callbacks()
622
623 batch.add(self.request1, callback=callbacks.f)
624 batch.add(self.request2, callback=callbacks.f)
625 http = HttpMockSequence([
626 ({'status': '200',
627 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
628 BATCH_RESPONSE),
629 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400630 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500631 self.assertEqual({'foo': 42}, callbacks.responses['1'])
632 self.assertEqual(None, callbacks.exceptions['1'])
633 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
634 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -0500635
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500636 def test_execute_request_body(self):
637 batch = BatchHttpRequest()
638
639 batch.add(self.request1)
640 batch.add(self.request2)
641 http = HttpMockSequence([
642 ({'status': '200',
643 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
644 'echo_request_body'),
645 ])
646 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400647 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500648 self.fail('Should raise exception')
649 except BatchError, e:
650 boundary, _ = e.content.split(None, 1)
651 self.assertEqual('--', boundary[:2])
652 parts = e.content.split(boundary)
653 self.assertEqual(4, len(parts))
654 self.assertEqual('', parts[0])
655 self.assertEqual('--', parts[3])
656 header = parts[1].splitlines()[1]
657 self.assertEqual('Content-Type: application/http', header)
658
Joe Gregorio654f4a22012-02-09 14:15:44 -0500659 def test_execute_refresh_and_retry_on_401(self):
660 batch = BatchHttpRequest()
661 callbacks = Callbacks()
662 cred_1 = MockCredentials('Foo')
663 cred_2 = MockCredentials('Bar')
664
665 http = HttpMockSequence([
666 ({'status': '200',
667 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
668 BATCH_RESPONSE_WITH_401),
669 ({'status': '200',
670 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
671 BATCH_SINGLE_RESPONSE),
672 ])
673
674 creds_http_1 = HttpMockSequence([])
675 cred_1.authorize(creds_http_1)
676
677 creds_http_2 = HttpMockSequence([])
678 cred_2.authorize(creds_http_2)
679
680 self.request1.http = creds_http_1
681 self.request2.http = creds_http_2
682
683 batch.add(self.request1, callback=callbacks.f)
684 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400685 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500686
687 self.assertEqual({'foo': 42}, callbacks.responses['1'])
688 self.assertEqual(None, callbacks.exceptions['1'])
689 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
690 self.assertEqual(None, callbacks.exceptions['2'])
691
692 self.assertEqual(1, cred_1._refreshed)
693 self.assertEqual(0, cred_2._refreshed)
694
695 self.assertEqual(1, cred_1._authorized)
696 self.assertEqual(1, cred_2._authorized)
697
698 self.assertEqual(1, cred_2._applied)
699 self.assertEqual(2, cred_1._applied)
700
701 def test_http_errors_passed_to_callback(self):
702 batch = BatchHttpRequest()
703 callbacks = Callbacks()
704 cred_1 = MockCredentials('Foo')
705 cred_2 = MockCredentials('Bar')
706
707 http = HttpMockSequence([
708 ({'status': '200',
709 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
710 BATCH_RESPONSE_WITH_401),
711 ({'status': '200',
712 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
713 BATCH_RESPONSE_WITH_401),
714 ])
715
716 creds_http_1 = HttpMockSequence([])
717 cred_1.authorize(creds_http_1)
718
719 creds_http_2 = HttpMockSequence([])
720 cred_2.authorize(creds_http_2)
721
722 self.request1.http = creds_http_1
723 self.request2.http = creds_http_2
724
725 batch.add(self.request1, callback=callbacks.f)
726 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400727 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500728
729 self.assertEqual(None, callbacks.responses['1'])
730 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -0400731 self.assertEqual(
732 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500733 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
734 self.assertEqual(None, callbacks.exceptions['2'])
735
Joe Gregorio66f57522011-11-30 11:00:00 -0500736 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500737 callbacks = Callbacks()
738 batch = BatchHttpRequest(callback=callbacks.f)
739
740 batch.add(self.request1)
741 batch.add(self.request2)
742 http = HttpMockSequence([
743 ({'status': '200',
744 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
745 BATCH_RESPONSE),
746 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400747 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500748 self.assertEqual({'foo': 42}, callbacks.responses['1'])
749 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500750
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400751 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -0400752 callbacks = Callbacks()
753 batch = BatchHttpRequest(callback=callbacks.f)
754
755 batch.add(self.request1)
756 batch.add(self.request2)
757 http = HttpMockSequence([
758 ({'status': '200',
759 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
760 BATCH_ERROR_RESPONSE),
761 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400762 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -0400763 self.assertEqual({'foo': 42}, callbacks.responses['1'])
764 expected = ('<HttpError 403 when requesting '
765 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
766 '"Access Not Configured">')
767 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -0500768
Joe Gregorio5c120db2012-08-23 09:13:55 -0400769
Joe Gregorioba5c7902012-08-03 12:48:16 -0400770class TestRequestUriTooLong(unittest.TestCase):
771
772 def test_turn_get_into_post(self):
773
774 def _postproc(resp, content):
775 return content
776
777 http = HttpMockSequence([
778 ({'status': '200'},
779 'echo_request_body'),
780 ({'status': '200'},
781 'echo_request_headers'),
782 ])
783
784 # Send a long query parameter.
785 query = {
786 'q': 'a' * MAX_URI_LENGTH + '?&'
787 }
788 req = HttpRequest(
789 http,
790 _postproc,
791 'http://example.com?' + urllib.urlencode(query),
792 method='GET',
793 body=None,
794 headers={},
795 methodId='foo',
796 resumable=None)
797
798 # Query parameters should be sent in the body.
799 response = req.execute()
800 self.assertEqual('q=' + 'a' * MAX_URI_LENGTH + '%3F%26', response)
801
802 # Extra headers should be set.
803 response = req.execute()
804 self.assertEqual('GET', response['x-http-method-override'])
805 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
806 self.assertEqual(
807 'application/x-www-form-urlencoded', response['content-type'])
808
Joe Gregorio5c120db2012-08-23 09:13:55 -0400809
810class TestStreamSlice(unittest.TestCase):
811 """Test _StreamSlice."""
812
813 def setUp(self):
814 self.stream = StringIO.StringIO('0123456789')
815
816 def test_read(self):
817 s = _StreamSlice(self.stream, 0, 4)
818 self.assertEqual('', s.read(0))
819 self.assertEqual('0', s.read(1))
820 self.assertEqual('123', s.read())
821
822 def test_read_too_much(self):
823 s = _StreamSlice(self.stream, 1, 4)
824 self.assertEqual('1234', s.read(6))
825
826 def test_read_all(self):
827 s = _StreamSlice(self.stream, 2, 1)
828 self.assertEqual('2', s.read(-1))
829
Ali Afshar164f37e2013-01-07 14:05:45 -0800830
831class TestResponseCallback(unittest.TestCase):
832 """Test adding callbacks to responses."""
833
834 def test_ensure_response_callback(self):
835 m = JsonModel()
836 request = HttpRequest(
837 None,
838 m.response,
839 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
840 method='POST',
841 body='{}',
842 headers={'content-type': 'application/json'})
843 h = HttpMockSequence([ ({'status': 200}, '{}')])
844 responses = []
845 def _on_response(resp, responses=responses):
846 responses.append(resp)
847 request.add_response_callback(_on_response)
848 request.execute(http=h)
849 self.assertEqual(1, len(responses))
850
851
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500852if __name__ == '__main__':
853 unittest.main()