blob: 37d8e9c4412a9158f544de115b7e55abdedf8b1f [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 Gregoriod0bd3882011-11-22 09:49:47 -050039from apiclient.http import MediaFileUpload
Joe Gregorio66f57522011-11-30 11:00:00 -050040from apiclient.http import MediaUpload
Ali Afshar6f11ea12012-02-07 10:32:14 -050041from apiclient.http import MediaInMemoryUpload
Joe Gregorio910b9b12012-06-12 09:36:30 -040042from apiclient.http import MediaIoBaseUpload
Joe Gregorio708388c2012-06-15 13:43:04 -040043from apiclient.http import MediaIoBaseDownload
Joe Gregorio66f57522011-11-30 11:00:00 -050044from apiclient.http import set_user_agent
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040045from apiclient.http import MAX_URI_LENGTH
Joe Gregorio66f57522011-11-30 11:00:00 -050046from apiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050047from oauth2client.client import Credentials
48
49
50class MockCredentials(Credentials):
51 """Mock class for all Credentials objects."""
52 def __init__(self, bearer_token):
53 super(MockCredentials, self).__init__()
54 self._authorized = 0
55 self._refreshed = 0
56 self._applied = 0
57 self._bearer_token = bearer_token
58
59 def authorize(self, http):
60 self._authorized += 1
61
62 request_orig = http.request
63
64 # The closure that will replace 'httplib2.Http.request'.
65 def new_request(uri, method='GET', body=None, headers=None,
66 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
67 connection_type=None):
68 # Modify the request headers to add the appropriate
69 # Authorization header.
70 if headers is None:
71 headers = {}
72 self.apply(headers)
73
74 resp, content = request_orig(uri, method, body, headers,
75 redirections, connection_type)
76
77 return resp, content
78
79 # Replace the request method with our own closure.
80 http.request = new_request
81
82 # Set credentials as a property of the request method.
83 setattr(http.request, 'credentials', self)
84
85 return http
86
87 def refresh(self, http):
88 self._refreshed += 1
89
90 def apply(self, headers):
91 self._applied += 1
92 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050093
94
Joe Gregoriod0bd3882011-11-22 09:49:47 -050095DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
96
97
98def datafile(filename):
99 return os.path.join(DATA_DIR, filename)
100
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500101class TestUserAgent(unittest.TestCase):
102
103 def test_set_user_agent(self):
104 http = HttpMockSequence([
105 ({'status': '200'}, 'echo_request_headers'),
106 ])
107
108 http = set_user_agent(http, "my_app/5.5")
109 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500110 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500111
112 def test_set_user_agent_nested(self):
113 http = HttpMockSequence([
114 ({'status': '200'}, 'echo_request_headers'),
115 ])
116
117 http = set_user_agent(http, "my_app/5.5")
118 http = set_user_agent(http, "my_library/0.1")
119 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500120 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500121
Joe Gregorio910b9b12012-06-12 09:36:30 -0400122
123class TestMediaUpload(unittest.TestCase):
124
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500125 def test_media_file_upload_to_from_json(self):
126 upload = MediaFileUpload(
127 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500128 self.assertEqual('image/png', upload.mimetype())
129 self.assertEqual(190, upload.size())
130 self.assertEqual(True, upload.resumable())
131 self.assertEqual(500, upload.chunksize())
132 self.assertEqual('PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500133
134 json = upload.to_json()
135 new_upload = MediaUpload.new_from_json(json)
136
Joe Gregorio654f4a22012-02-09 14:15:44 -0500137 self.assertEqual('image/png', new_upload.mimetype())
138 self.assertEqual(190, new_upload.size())
139 self.assertEqual(True, new_upload.resumable())
140 self.assertEqual(500, new_upload.chunksize())
141 self.assertEqual('PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500142
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400143 def test_media_file_upload_raises_on_invalid_chunksize(self):
144 self.assertRaises(InvalidChunkSizeError, MediaFileUpload,
145 datafile('small.png'), mimetype='image/png', chunksize=-2,
146 resumable=True)
147
Ali Afshar1cb6b672012-03-12 08:46:14 -0400148 def test_media_inmemory_upload(self):
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400149 media = MediaInMemoryUpload('abcdef', mimetype='text/plain', chunksize=10,
Ali Afshar1cb6b672012-03-12 08:46:14 -0400150 resumable=True)
151 self.assertEqual('text/plain', media.mimetype())
152 self.assertEqual(10, media.chunksize())
153 self.assertTrue(media.resumable())
154 self.assertEqual('bc', media.getbytes(1, 2))
155 self.assertEqual(6, media.size())
156
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500157 def test_http_request_to_from_json(self):
158
159 def _postproc(*kwargs):
160 pass
161
162 http = httplib2.Http()
163 media_upload = MediaFileUpload(
164 datafile('small.png'), chunksize=500, resumable=True)
165 req = HttpRequest(
166 http,
167 _postproc,
168 'http://example.com',
169 method='POST',
170 body='{}',
171 headers={'content-type': 'multipart/related; boundary="---flubber"'},
172 methodId='foo',
173 resumable=media_upload)
174
175 json = req.to_json()
176 new_req = HttpRequest.from_json(json, http, _postproc)
177
Joe Gregorio654f4a22012-02-09 14:15:44 -0500178 self.assertEqual({'content-type':
179 'multipart/related; boundary="---flubber"'},
180 new_req.headers)
181 self.assertEqual('http://example.com', new_req.uri)
182 self.assertEqual('{}', new_req.body)
183 self.assertEqual(http, new_req.http)
184 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500185
Joe Gregorio910b9b12012-06-12 09:36:30 -0400186
187class TestMediaIoBaseUpload(unittest.TestCase):
188
189 def test_media_io_base_upload_from_file_io(self):
190 try:
191 import io
192
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400193 fd = io.FileIO(datafile('small.png'), 'r')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400194 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400195 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400196 self.assertEqual('image/png', upload.mimetype())
197 self.assertEqual(190, upload.size())
198 self.assertEqual(True, upload.resumable())
199 self.assertEqual(500, upload.chunksize())
200 self.assertEqual('PNG', upload.getbytes(1, 3))
201 except ImportError:
202 pass
203
204 def test_media_io_base_upload_from_file_object(self):
205 f = open(datafile('small.png'), 'r')
206 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400207 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400208 self.assertEqual('image/png', upload.mimetype())
209 self.assertEqual(190, upload.size())
210 self.assertEqual(True, upload.resumable())
211 self.assertEqual(500, upload.chunksize())
212 self.assertEqual('PNG', upload.getbytes(1, 3))
213 f.close()
214
215 def test_media_io_base_upload_serializable(self):
216 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400217 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400218
219 try:
220 json = upload.to_json()
221 self.fail('MediaIoBaseUpload should not be serializable.')
222 except NotImplementedError:
223 pass
224
225 def test_media_io_base_upload_from_string_io(self):
226 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400227 fd = StringIO.StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400228 f.close()
229
230 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400231 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400232 self.assertEqual('image/png', upload.mimetype())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400233 self.assertEqual(190, upload.size())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400234 self.assertEqual(True, upload.resumable())
235 self.assertEqual(500, upload.chunksize())
236 self.assertEqual('PNG', upload.getbytes(1, 3))
237 f.close()
238
239 def test_media_io_base_upload_from_bytes(self):
240 try:
241 import io
242
243 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400244 fd = io.BytesIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400245 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())
251 self.assertEqual('PNG', upload.getbytes(1, 3))
252 except ImportError:
253 pass
254
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400255 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
256 try:
257 import io
258
259 f = open(datafile('small.png'), 'r')
260 fd = io.BytesIO(f.read())
261 self.assertRaises(InvalidChunkSizeError, MediaIoBaseUpload,
262 fd, 'image/png', chunksize=-2, resumable=True)
263 except ImportError:
264 pass
265
266 def test_media_io_base_upload_streamable(self):
267 try:
268 import io
269
270 fd = io.BytesIO('stuff')
271 upload = MediaIoBaseUpload(
272 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
273 self.assertEqual(True, upload.has_stream())
274 self.assertEqual(fd, upload.stream())
275 except ImportError:
276 pass
277
Joe Gregorio910b9b12012-06-12 09:36:30 -0400278
Joe Gregorio708388c2012-06-15 13:43:04 -0400279class TestMediaIoBaseDownload(unittest.TestCase):
280
281 def setUp(self):
282 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400283 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -0400284 self.request = zoo.animals().get_media(name='Lion')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400285 self.fd = StringIO.StringIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400286
287 def test_media_io_base_download(self):
288 self.request.http = HttpMockSequence([
289 ({'status': '200',
290 'content-range': '0-2/5'}, '123'),
291 ({'status': '200',
292 'content-range': '3-4/5'}, '45'),
293 ])
294
295 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400296 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400297
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400298 self.assertEqual(self.fd, download._fd)
299 self.assertEqual(3, download._chunksize)
300 self.assertEqual(0, download._progress)
301 self.assertEqual(None, download._total_size)
302 self.assertEqual(False, download._done)
303 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400304
305 status, done = download.next_chunk()
306
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400307 self.assertEqual(self.fd.getvalue(), '123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400308 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400309 self.assertEqual(3, download._progress)
310 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400311 self.assertEqual(3, status.resumable_progress)
312
313 status, done = download.next_chunk()
314
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400315 self.assertEqual(self.fd.getvalue(), '12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400316 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400317 self.assertEqual(5, download._progress)
318 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400319
320 def test_media_io_base_download_handle_redirects(self):
321 self.request.http = HttpMockSequence([
322 ({'status': '307',
323 'location': 'https://secure.example.net/lion'}, ''),
324 ({'status': '200',
325 'content-range': '0-2/5'}, 'abc'),
326 ])
327
328 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400329 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400330
331 status, done = download.next_chunk()
332
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400333 self.assertEqual('https://secure.example.net/lion', download._uri)
334 self.assertEqual(self.fd.getvalue(), 'abc')
Joe Gregorio708388c2012-06-15 13:43:04 -0400335 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400336 self.assertEqual(3, download._progress)
337 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400338
339 def test_media_io_base_download_handle_4xx(self):
340 self.request.http = HttpMockSequence([
341 ({'status': '400'}, ''),
342 ])
343
344 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400345 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400346
347 try:
348 status, done = download.next_chunk()
349 self.fail('Should raise an exception')
350 except HttpError:
351 pass
352
353 # Even after raising an exception we can pick up where we left off.
354 self.request.http = HttpMockSequence([
355 ({'status': '200',
356 'content-range': '0-2/5'}, '123'),
357 ])
358
359 status, done = download.next_chunk()
360
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400361 self.assertEqual(self.fd.getvalue(), '123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400362
Joe Gregorio66f57522011-11-30 11:00:00 -0500363EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
364Content-Type: application/json
365MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500366Host: www.googleapis.com
367content-length: 2\r\n\r\n{}"""
368
369
370NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
371Content-Type: application/json
372MIME-Version: 1.0
373Host: www.googleapis.com
374content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500375
376
377RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400378Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500379Content-Length: 14
380ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
381
382
383BATCH_RESPONSE = """--batch_foobarbaz
384Content-Type: application/http
385Content-Transfer-Encoding: binary
386Content-ID: <randomness+1>
387
388HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400389Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500390Content-Length: 14
391ETag: "etag/pony"\r\n\r\n{"foo": 42}
392
393--batch_foobarbaz
394Content-Type: application/http
395Content-Transfer-Encoding: binary
396Content-ID: <randomness+2>
397
398HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400399Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500400Content-Length: 14
401ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
402--batch_foobarbaz--"""
403
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500404
Joe Gregorio3fb93672012-07-25 11:31:11 -0400405BATCH_ERROR_RESPONSE = """--batch_foobarbaz
406Content-Type: application/http
407Content-Transfer-Encoding: binary
408Content-ID: <randomness+1>
409
410HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400411Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400412Content-Length: 14
413ETag: "etag/pony"\r\n\r\n{"foo": 42}
414
415--batch_foobarbaz
416Content-Type: application/http
417Content-Transfer-Encoding: binary
418Content-ID: <randomness+2>
419
420HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400421Content-Type: application/json
422Content-Length: 245
423ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400424 "error": {
425 "errors": [
426 {
427 "domain": "usageLimits",
428 "reason": "accessNotConfigured",
429 "message": "Access Not Configured",
430 "debugInfo": "QuotaState: BLOCKED"
431 }
432 ],
433 "code": 403,
434 "message": "Access Not Configured"
435 }
436}
437
438--batch_foobarbaz--"""
439
440
Joe Gregorio654f4a22012-02-09 14:15:44 -0500441BATCH_RESPONSE_WITH_401 = """--batch_foobarbaz
442Content-Type: application/http
443Content-Transfer-Encoding: binary
444Content-ID: <randomness+1>
445
Joe Gregorioc752e332012-07-11 14:43:52 -0400446HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400447Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500448Content-Length: 14
449ETag: "etag/pony"\r\n\r\n{"error": {"message":
450 "Authorizaton failed."}}
451
452--batch_foobarbaz
453Content-Type: application/http
454Content-Transfer-Encoding: binary
455Content-ID: <randomness+2>
456
457HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400458Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500459Content-Length: 14
460ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
461--batch_foobarbaz--"""
462
463
464BATCH_SINGLE_RESPONSE = """--batch_foobarbaz
465Content-Type: application/http
466Content-Transfer-Encoding: binary
467Content-ID: <randomness+1>
468
469HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400470Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500471Content-Length: 14
472ETag: "etag/pony"\r\n\r\n{"foo": 42}
473--batch_foobarbaz--"""
474
475class Callbacks(object):
476 def __init__(self):
477 self.responses = {}
478 self.exceptions = {}
479
480 def f(self, request_id, response, exception):
481 self.responses[request_id] = response
482 self.exceptions[request_id] = exception
483
484
Joe Gregorio66f57522011-11-30 11:00:00 -0500485class TestBatch(unittest.TestCase):
486
487 def setUp(self):
488 model = JsonModel()
489 self.request1 = HttpRequest(
490 None,
491 model.response,
492 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
493 method='POST',
494 body='{}',
495 headers={'content-type': 'application/json'})
496
497 self.request2 = HttpRequest(
498 None,
499 model.response,
500 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500501 method='GET',
502 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500503 headers={'content-type': 'application/json'})
504
505
506 def test_id_to_from_content_id_header(self):
507 batch = BatchHttpRequest()
508 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
509
510 def test_invalid_content_id_header(self):
511 batch = BatchHttpRequest()
512 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
513 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
514 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
515
516 def test_serialize_request(self):
517 batch = BatchHttpRequest()
518 request = HttpRequest(
519 None,
520 None,
521 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
522 method='POST',
523 body='{}',
524 headers={'content-type': 'application/json'},
525 methodId=None,
526 resumable=None)
527 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500528 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500529
Joe Gregoriodd813822012-01-25 10:32:47 -0500530 def test_serialize_request_media_body(self):
531 batch = BatchHttpRequest()
532 f = open(datafile('small.png'))
533 body = f.read()
534 f.close()
535
536 request = HttpRequest(
537 None,
538 None,
539 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
540 method='POST',
541 body=body,
542 headers={'content-type': 'application/json'},
543 methodId=None,
544 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500545 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -0500546 s = batch._serialize_request(request).splitlines()
547
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500548 def test_serialize_request_no_body(self):
549 batch = BatchHttpRequest()
550 request = HttpRequest(
551 None,
552 None,
553 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
554 method='POST',
555 body='',
556 headers={'content-type': 'application/json'},
557 methodId=None,
558 resumable=None)
559 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500560 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500561
Joe Gregorio66f57522011-11-30 11:00:00 -0500562 def test_deserialize_response(self):
563 batch = BatchHttpRequest()
564 resp, content = batch._deserialize_response(RESPONSE)
565
Joe Gregorio654f4a22012-02-09 14:15:44 -0500566 self.assertEqual(200, resp.status)
567 self.assertEqual('OK', resp.reason)
568 self.assertEqual(11, resp.version)
569 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -0500570
571 def test_new_id(self):
572 batch = BatchHttpRequest()
573
574 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500575 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500576
577 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500578 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500579
580 batch.add(self.request1, request_id='3')
581
582 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500583 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500584
585 def test_add(self):
586 batch = BatchHttpRequest()
587 batch.add(self.request1, request_id='1')
588 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
589
590 def test_add_fail_for_resumable(self):
591 batch = BatchHttpRequest()
592
593 upload = MediaFileUpload(
594 datafile('small.png'), chunksize=500, resumable=True)
595 self.request1.resumable = upload
596 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
597
598 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500599 batch = BatchHttpRequest()
600 callbacks = Callbacks()
601
602 batch.add(self.request1, callback=callbacks.f)
603 batch.add(self.request2, callback=callbacks.f)
604 http = HttpMockSequence([
605 ({'status': '200',
606 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
607 BATCH_RESPONSE),
608 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400609 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500610 self.assertEqual({'foo': 42}, callbacks.responses['1'])
611 self.assertEqual(None, callbacks.exceptions['1'])
612 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
613 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -0500614
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500615 def test_execute_request_body(self):
616 batch = BatchHttpRequest()
617
618 batch.add(self.request1)
619 batch.add(self.request2)
620 http = HttpMockSequence([
621 ({'status': '200',
622 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
623 'echo_request_body'),
624 ])
625 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400626 batch.execute(http=http)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500627 self.fail('Should raise exception')
628 except BatchError, e:
629 boundary, _ = e.content.split(None, 1)
630 self.assertEqual('--', boundary[:2])
631 parts = e.content.split(boundary)
632 self.assertEqual(4, len(parts))
633 self.assertEqual('', parts[0])
634 self.assertEqual('--', parts[3])
635 header = parts[1].splitlines()[1]
636 self.assertEqual('Content-Type: application/http', header)
637
Joe Gregorio654f4a22012-02-09 14:15:44 -0500638 def test_execute_refresh_and_retry_on_401(self):
639 batch = BatchHttpRequest()
640 callbacks = Callbacks()
641 cred_1 = MockCredentials('Foo')
642 cred_2 = MockCredentials('Bar')
643
644 http = HttpMockSequence([
645 ({'status': '200',
646 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
647 BATCH_RESPONSE_WITH_401),
648 ({'status': '200',
649 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
650 BATCH_SINGLE_RESPONSE),
651 ])
652
653 creds_http_1 = HttpMockSequence([])
654 cred_1.authorize(creds_http_1)
655
656 creds_http_2 = HttpMockSequence([])
657 cred_2.authorize(creds_http_2)
658
659 self.request1.http = creds_http_1
660 self.request2.http = creds_http_2
661
662 batch.add(self.request1, callback=callbacks.f)
663 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400664 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500665
666 self.assertEqual({'foo': 42}, callbacks.responses['1'])
667 self.assertEqual(None, callbacks.exceptions['1'])
668 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
669 self.assertEqual(None, callbacks.exceptions['2'])
670
671 self.assertEqual(1, cred_1._refreshed)
672 self.assertEqual(0, cred_2._refreshed)
673
674 self.assertEqual(1, cred_1._authorized)
675 self.assertEqual(1, cred_2._authorized)
676
677 self.assertEqual(1, cred_2._applied)
678 self.assertEqual(2, cred_1._applied)
679
680 def test_http_errors_passed_to_callback(self):
681 batch = BatchHttpRequest()
682 callbacks = Callbacks()
683 cred_1 = MockCredentials('Foo')
684 cred_2 = MockCredentials('Bar')
685
686 http = HttpMockSequence([
687 ({'status': '200',
688 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
689 BATCH_RESPONSE_WITH_401),
690 ({'status': '200',
691 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
692 BATCH_RESPONSE_WITH_401),
693 ])
694
695 creds_http_1 = HttpMockSequence([])
696 cred_1.authorize(creds_http_1)
697
698 creds_http_2 = HttpMockSequence([])
699 cred_2.authorize(creds_http_2)
700
701 self.request1.http = creds_http_1
702 self.request2.http = creds_http_2
703
704 batch.add(self.request1, callback=callbacks.f)
705 batch.add(self.request2, callback=callbacks.f)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400706 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500707
708 self.assertEqual(None, callbacks.responses['1'])
709 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -0400710 self.assertEqual(
711 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500712 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
713 self.assertEqual(None, callbacks.exceptions['2'])
714
Joe Gregorio66f57522011-11-30 11:00:00 -0500715 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500716 callbacks = Callbacks()
717 batch = BatchHttpRequest(callback=callbacks.f)
718
719 batch.add(self.request1)
720 batch.add(self.request2)
721 http = HttpMockSequence([
722 ({'status': '200',
723 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
724 BATCH_RESPONSE),
725 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400726 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500727 self.assertEqual({'foo': 42}, callbacks.responses['1'])
728 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500729
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400730 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -0400731 callbacks = Callbacks()
732 batch = BatchHttpRequest(callback=callbacks.f)
733
734 batch.add(self.request1)
735 batch.add(self.request2)
736 http = HttpMockSequence([
737 ({'status': '200',
738 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
739 BATCH_ERROR_RESPONSE),
740 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400741 batch.execute(http=http)
Joe Gregorio3fb93672012-07-25 11:31:11 -0400742 self.assertEqual({'foo': 42}, callbacks.responses['1'])
743 expected = ('<HttpError 403 when requesting '
744 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
745 '"Access Not Configured">')
746 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -0500747
Joe Gregorioba5c7902012-08-03 12:48:16 -0400748class TestRequestUriTooLong(unittest.TestCase):
749
750 def test_turn_get_into_post(self):
751
752 def _postproc(resp, content):
753 return content
754
755 http = HttpMockSequence([
756 ({'status': '200'},
757 'echo_request_body'),
758 ({'status': '200'},
759 'echo_request_headers'),
760 ])
761
762 # Send a long query parameter.
763 query = {
764 'q': 'a' * MAX_URI_LENGTH + '?&'
765 }
766 req = HttpRequest(
767 http,
768 _postproc,
769 'http://example.com?' + urllib.urlencode(query),
770 method='GET',
771 body=None,
772 headers={},
773 methodId='foo',
774 resumable=None)
775
776 # Query parameters should be sent in the body.
777 response = req.execute()
778 self.assertEqual('q=' + 'a' * MAX_URI_LENGTH + '%3F%26', response)
779
780 # Extra headers should be set.
781 response = req.execute()
782 self.assertEqual('GET', response['x-http-method-override'])
783 self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length'])
784 self.assertEqual(
785 'application/x-www-form-urlencoded', response['content-type'])
786
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500787if __name__ == '__main__':
788 unittest.main()