blob: d067afee985bfe71d410f1f28b7a4c0ccb57c729 [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 Gregorio910b9b12012-06-12 09:36:30 -040028import StringIO
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050029
Joe Gregorio708388c2012-06-15 13:43:04 -040030from apiclient.discovery import build
Joe Gregorio66f57522011-11-30 11:00:00 -050031from apiclient.errors import BatchError
Joe Gregorio708388c2012-06-15 13:43:04 -040032from apiclient.errors import HttpError
Joe Gregorio66f57522011-11-30 11:00:00 -050033from apiclient.http import BatchHttpRequest
Joe Gregorio708388c2012-06-15 13:43:04 -040034from apiclient.http import HttpMock
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050035from apiclient.http import HttpMockSequence
Joe Gregoriod0bd3882011-11-22 09:49:47 -050036from apiclient.http import HttpRequest
Joe Gregoriod0bd3882011-11-22 09:49:47 -050037from apiclient.http import MediaFileUpload
Joe Gregorio66f57522011-11-30 11:00:00 -050038from apiclient.http import MediaUpload
Ali Afshar6f11ea12012-02-07 10:32:14 -050039from apiclient.http import MediaInMemoryUpload
Joe Gregorio910b9b12012-06-12 09:36:30 -040040from apiclient.http import MediaIoBaseUpload
Joe Gregorio708388c2012-06-15 13:43:04 -040041from apiclient.http import MediaIoBaseDownload
Joe Gregorio66f57522011-11-30 11:00:00 -050042from apiclient.http import set_user_agent
43from apiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050044from oauth2client.client import Credentials
45
46
47class MockCredentials(Credentials):
48 """Mock class for all Credentials objects."""
49 def __init__(self, bearer_token):
50 super(MockCredentials, self).__init__()
51 self._authorized = 0
52 self._refreshed = 0
53 self._applied = 0
54 self._bearer_token = bearer_token
55
56 def authorize(self, http):
57 self._authorized += 1
58
59 request_orig = http.request
60
61 # The closure that will replace 'httplib2.Http.request'.
62 def new_request(uri, method='GET', body=None, headers=None,
63 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
64 connection_type=None):
65 # Modify the request headers to add the appropriate
66 # Authorization header.
67 if headers is None:
68 headers = {}
69 self.apply(headers)
70
71 resp, content = request_orig(uri, method, body, headers,
72 redirections, connection_type)
73
74 return resp, content
75
76 # Replace the request method with our own closure.
77 http.request = new_request
78
79 # Set credentials as a property of the request method.
80 setattr(http.request, 'credentials', self)
81
82 return http
83
84 def refresh(self, http):
85 self._refreshed += 1
86
87 def apply(self, headers):
88 self._applied += 1
89 headers['authorization'] = self._bearer_token + ' ' + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050090
91
Joe Gregoriod0bd3882011-11-22 09:49:47 -050092DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
93
94
95def datafile(filename):
96 return os.path.join(DATA_DIR, filename)
97
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050098class TestUserAgent(unittest.TestCase):
99
100 def test_set_user_agent(self):
101 http = HttpMockSequence([
102 ({'status': '200'}, 'echo_request_headers'),
103 ])
104
105 http = set_user_agent(http, "my_app/5.5")
106 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500107 self.assertEqual('my_app/5.5', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500108
109 def test_set_user_agent_nested(self):
110 http = HttpMockSequence([
111 ({'status': '200'}, 'echo_request_headers'),
112 ])
113
114 http = set_user_agent(http, "my_app/5.5")
115 http = set_user_agent(http, "my_library/0.1")
116 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500117 self.assertEqual('my_app/5.5 my_library/0.1', content['user-agent'])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500118
Joe Gregorio910b9b12012-06-12 09:36:30 -0400119
120class TestMediaUpload(unittest.TestCase):
121
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500122 def test_media_file_upload_to_from_json(self):
123 upload = MediaFileUpload(
124 datafile('small.png'), chunksize=500, resumable=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500125 self.assertEqual('image/png', upload.mimetype())
126 self.assertEqual(190, upload.size())
127 self.assertEqual(True, upload.resumable())
128 self.assertEqual(500, upload.chunksize())
129 self.assertEqual('PNG', upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500130
131 json = upload.to_json()
132 new_upload = MediaUpload.new_from_json(json)
133
Joe Gregorio654f4a22012-02-09 14:15:44 -0500134 self.assertEqual('image/png', new_upload.mimetype())
135 self.assertEqual(190, new_upload.size())
136 self.assertEqual(True, new_upload.resumable())
137 self.assertEqual(500, new_upload.chunksize())
138 self.assertEqual('PNG', new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500139
Ali Afshar1cb6b672012-03-12 08:46:14 -0400140 def test_media_inmemory_upload(self):
141 media = MediaInMemoryUpload('abcdef', 'text/plain', chunksize=10,
142 resumable=True)
143 self.assertEqual('text/plain', media.mimetype())
144 self.assertEqual(10, media.chunksize())
145 self.assertTrue(media.resumable())
146 self.assertEqual('bc', media.getbytes(1, 2))
147 self.assertEqual(6, media.size())
148
149 def test_media_inmemory_upload_json_roundtrip(self):
150 media = MediaInMemoryUpload(os.urandom(64), 'text/plain', chunksize=10,
151 resumable=True)
152 data = media.to_json()
153 newmedia = MediaInMemoryUpload.new_from_json(data)
154 self.assertEqual(media._body, newmedia._body)
155 self.assertEqual(media._chunksize, newmedia._chunksize)
156 self.assertEqual(media._resumable, newmedia._resumable)
157 self.assertEqual(media._mimetype, newmedia._mimetype)
158
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500159 def test_http_request_to_from_json(self):
160
161 def _postproc(*kwargs):
162 pass
163
164 http = httplib2.Http()
165 media_upload = MediaFileUpload(
166 datafile('small.png'), chunksize=500, resumable=True)
167 req = HttpRequest(
168 http,
169 _postproc,
170 'http://example.com',
171 method='POST',
172 body='{}',
173 headers={'content-type': 'multipart/related; boundary="---flubber"'},
174 methodId='foo',
175 resumable=media_upload)
176
177 json = req.to_json()
178 new_req = HttpRequest.from_json(json, http, _postproc)
179
Joe Gregorio654f4a22012-02-09 14:15:44 -0500180 self.assertEqual({'content-type':
181 'multipart/related; boundary="---flubber"'},
182 new_req.headers)
183 self.assertEqual('http://example.com', new_req.uri)
184 self.assertEqual('{}', new_req.body)
185 self.assertEqual(http, new_req.http)
186 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500187
Joe Gregorio910b9b12012-06-12 09:36:30 -0400188
189class TestMediaIoBaseUpload(unittest.TestCase):
190
191 def test_media_io_base_upload_from_file_io(self):
192 try:
193 import io
194
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400195 fd = io.FileIO(datafile('small.png'), 'r')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400196 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400197 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400198 self.assertEqual('image/png', upload.mimetype())
199 self.assertEqual(190, upload.size())
200 self.assertEqual(True, upload.resumable())
201 self.assertEqual(500, upload.chunksize())
202 self.assertEqual('PNG', upload.getbytes(1, 3))
203 except ImportError:
204 pass
205
206 def test_media_io_base_upload_from_file_object(self):
207 f = open(datafile('small.png'), 'r')
208 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400209 fd=f, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400210 self.assertEqual('image/png', upload.mimetype())
211 self.assertEqual(190, upload.size())
212 self.assertEqual(True, upload.resumable())
213 self.assertEqual(500, upload.chunksize())
214 self.assertEqual('PNG', upload.getbytes(1, 3))
215 f.close()
216
217 def test_media_io_base_upload_serializable(self):
218 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400219 upload = MediaIoBaseUpload(fd=f, mimetype='image/png')
Joe Gregorio910b9b12012-06-12 09:36:30 -0400220
221 try:
222 json = upload.to_json()
223 self.fail('MediaIoBaseUpload should not be serializable.')
224 except NotImplementedError:
225 pass
226
227 def test_media_io_base_upload_from_string_io(self):
228 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400229 fd = StringIO.StringIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400230 f.close()
231
232 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400233 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400234 self.assertEqual('image/png', upload.mimetype())
235 self.assertEqual(None, upload.size())
236 self.assertEqual(True, upload.resumable())
237 self.assertEqual(500, upload.chunksize())
238 self.assertEqual('PNG', upload.getbytes(1, 3))
239 f.close()
240
241 def test_media_io_base_upload_from_bytes(self):
242 try:
243 import io
244
245 f = open(datafile('small.png'), 'r')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400246 fd = io.BytesIO(f.read())
Joe Gregorio910b9b12012-06-12 09:36:30 -0400247 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400248 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400249 self.assertEqual('image/png', upload.mimetype())
250 self.assertEqual(None, upload.size())
251 self.assertEqual(True, upload.resumable())
252 self.assertEqual(500, upload.chunksize())
253 self.assertEqual('PNG', upload.getbytes(1, 3))
254 except ImportError:
255 pass
256
257
Joe Gregorio708388c2012-06-15 13:43:04 -0400258class TestMediaIoBaseDownload(unittest.TestCase):
259
260 def setUp(self):
261 http = HttpMock(datafile('zoo.json'), {'status': '200'})
262 zoo = build('zoo', 'v1', http)
263 self.request = zoo.animals().get_media(name='Lion')
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400264 self.fd = StringIO.StringIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400265
266 def test_media_io_base_download(self):
267 self.request.http = HttpMockSequence([
268 ({'status': '200',
269 'content-range': '0-2/5'}, '123'),
270 ({'status': '200',
271 'content-range': '3-4/5'}, '45'),
272 ])
273
274 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400275 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400276
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400277 self.assertEqual(self.fd, download._fd)
278 self.assertEqual(3, download._chunksize)
279 self.assertEqual(0, download._progress)
280 self.assertEqual(None, download._total_size)
281 self.assertEqual(False, download._done)
282 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400283
284 status, done = download.next_chunk()
285
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400286 self.assertEqual(self.fd.getvalue(), '123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400287 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400288 self.assertEqual(3, download._progress)
289 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400290 self.assertEqual(3, status.resumable_progress)
291
292 status, done = download.next_chunk()
293
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400294 self.assertEqual(self.fd.getvalue(), '12345')
Joe Gregorio708388c2012-06-15 13:43:04 -0400295 self.assertEqual(True, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400296 self.assertEqual(5, download._progress)
297 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400298
299 def test_media_io_base_download_handle_redirects(self):
300 self.request.http = HttpMockSequence([
301 ({'status': '307',
302 'location': 'https://secure.example.net/lion'}, ''),
303 ({'status': '200',
304 'content-range': '0-2/5'}, 'abc'),
305 ])
306
307 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400308 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400309
310 status, done = download.next_chunk()
311
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400312 self.assertEqual('https://secure.example.net/lion', download._uri)
313 self.assertEqual(self.fd.getvalue(), 'abc')
Joe Gregorio708388c2012-06-15 13:43:04 -0400314 self.assertEqual(False, done)
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400315 self.assertEqual(3, download._progress)
316 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400317
318 def test_media_io_base_download_handle_4xx(self):
319 self.request.http = HttpMockSequence([
320 ({'status': '400'}, ''),
321 ])
322
323 download = MediaIoBaseDownload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400324 fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400325
326 try:
327 status, done = download.next_chunk()
328 self.fail('Should raise an exception')
329 except HttpError:
330 pass
331
332 # Even after raising an exception we can pick up where we left off.
333 self.request.http = HttpMockSequence([
334 ({'status': '200',
335 'content-range': '0-2/5'}, '123'),
336 ])
337
338 status, done = download.next_chunk()
339
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400340 self.assertEqual(self.fd.getvalue(), '123')
Joe Gregorio708388c2012-06-15 13:43:04 -0400341
Joe Gregorio66f57522011-11-30 11:00:00 -0500342EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
343Content-Type: application/json
344MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500345Host: www.googleapis.com
346content-length: 2\r\n\r\n{}"""
347
348
349NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
350Content-Type: application/json
351MIME-Version: 1.0
352Host: www.googleapis.com
353content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500354
355
356RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400357Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500358Content-Length: 14
359ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
360
361
362BATCH_RESPONSE = """--batch_foobarbaz
363Content-Type: application/http
364Content-Transfer-Encoding: binary
365Content-ID: <randomness+1>
366
367HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400368Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500369Content-Length: 14
370ETag: "etag/pony"\r\n\r\n{"foo": 42}
371
372--batch_foobarbaz
373Content-Type: application/http
374Content-Transfer-Encoding: binary
375Content-ID: <randomness+2>
376
377HTTP/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/sheep"\r\n\r\n{"baz": "qux"}
381--batch_foobarbaz--"""
382
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500383
Joe Gregorio3fb93672012-07-25 11:31:11 -0400384BATCH_ERROR_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 Gregorio3fb93672012-07-25 11:31:11 -0400391Content-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 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400400Content-Type: application/json
401Content-Length: 245
402ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400403 "error": {
404 "errors": [
405 {
406 "domain": "usageLimits",
407 "reason": "accessNotConfigured",
408 "message": "Access Not Configured",
409 "debugInfo": "QuotaState: BLOCKED"
410 }
411 ],
412 "code": 403,
413 "message": "Access Not Configured"
414 }
415}
416
417--batch_foobarbaz--"""
418
419
Joe Gregorio654f4a22012-02-09 14:15:44 -0500420BATCH_RESPONSE_WITH_401 = """--batch_foobarbaz
421Content-Type: application/http
422Content-Transfer-Encoding: binary
423Content-ID: <randomness+1>
424
Joe Gregorioc752e332012-07-11 14:43:52 -0400425HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400426Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500427Content-Length: 14
428ETag: "etag/pony"\r\n\r\n{"error": {"message":
429 "Authorizaton failed."}}
430
431--batch_foobarbaz
432Content-Type: application/http
433Content-Transfer-Encoding: binary
434Content-ID: <randomness+2>
435
436HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400437Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500438Content-Length: 14
439ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
440--batch_foobarbaz--"""
441
442
443BATCH_SINGLE_RESPONSE = """--batch_foobarbaz
444Content-Type: application/http
445Content-Transfer-Encoding: binary
446Content-ID: <randomness+1>
447
448HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400449Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500450Content-Length: 14
451ETag: "etag/pony"\r\n\r\n{"foo": 42}
452--batch_foobarbaz--"""
453
454class Callbacks(object):
455 def __init__(self):
456 self.responses = {}
457 self.exceptions = {}
458
459 def f(self, request_id, response, exception):
460 self.responses[request_id] = response
461 self.exceptions[request_id] = exception
462
463
Joe Gregorio66f57522011-11-30 11:00:00 -0500464class TestBatch(unittest.TestCase):
465
466 def setUp(self):
467 model = JsonModel()
468 self.request1 = HttpRequest(
469 None,
470 model.response,
471 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
472 method='POST',
473 body='{}',
474 headers={'content-type': 'application/json'})
475
476 self.request2 = HttpRequest(
477 None,
478 model.response,
479 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500480 method='GET',
481 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500482 headers={'content-type': 'application/json'})
483
484
485 def test_id_to_from_content_id_header(self):
486 batch = BatchHttpRequest()
487 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
488
489 def test_invalid_content_id_header(self):
490 batch = BatchHttpRequest()
491 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
492 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
493 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
494
495 def test_serialize_request(self):
496 batch = BatchHttpRequest()
497 request = HttpRequest(
498 None,
499 None,
500 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
501 method='POST',
502 body='{}',
503 headers={'content-type': 'application/json'},
504 methodId=None,
505 resumable=None)
506 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500507 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -0500508
Joe Gregoriodd813822012-01-25 10:32:47 -0500509 def test_serialize_request_media_body(self):
510 batch = BatchHttpRequest()
511 f = open(datafile('small.png'))
512 body = f.read()
513 f.close()
514
515 request = HttpRequest(
516 None,
517 None,
518 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
519 method='POST',
520 body=body,
521 headers={'content-type': 'application/json'},
522 methodId=None,
523 resumable=None)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500524 # Just testing it shouldn't raise an exception.
Joe Gregoriodd813822012-01-25 10:32:47 -0500525 s = batch._serialize_request(request).splitlines()
526
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500527 def test_serialize_request_no_body(self):
528 batch = BatchHttpRequest()
529 request = HttpRequest(
530 None,
531 None,
532 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
533 method='POST',
534 body='',
535 headers={'content-type': 'application/json'},
536 methodId=None,
537 resumable=None)
538 s = batch._serialize_request(request).splitlines()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500539 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500540
Joe Gregorio66f57522011-11-30 11:00:00 -0500541 def test_deserialize_response(self):
542 batch = BatchHttpRequest()
543 resp, content = batch._deserialize_response(RESPONSE)
544
Joe Gregorio654f4a22012-02-09 14:15:44 -0500545 self.assertEqual(200, resp.status)
546 self.assertEqual('OK', resp.reason)
547 self.assertEqual(11, resp.version)
548 self.assertEqual('{"answer": 42}', content)
Joe Gregorio66f57522011-11-30 11:00:00 -0500549
550 def test_new_id(self):
551 batch = BatchHttpRequest()
552
553 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500554 self.assertEqual('1', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500555
556 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500557 self.assertEqual('2', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500558
559 batch.add(self.request1, request_id='3')
560
561 id_ = batch._new_id()
Joe Gregorio654f4a22012-02-09 14:15:44 -0500562 self.assertEqual('4', id_)
Joe Gregorio66f57522011-11-30 11:00:00 -0500563
564 def test_add(self):
565 batch = BatchHttpRequest()
566 batch.add(self.request1, request_id='1')
567 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
568
569 def test_add_fail_for_resumable(self):
570 batch = BatchHttpRequest()
571
572 upload = MediaFileUpload(
573 datafile('small.png'), chunksize=500, resumable=True)
574 self.request1.resumable = upload
575 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
576
577 def test_execute(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500578 batch = BatchHttpRequest()
579 callbacks = Callbacks()
580
581 batch.add(self.request1, callback=callbacks.f)
582 batch.add(self.request2, callback=callbacks.f)
583 http = HttpMockSequence([
584 ({'status': '200',
585 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
586 BATCH_RESPONSE),
587 ])
588 batch.execute(http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500589 self.assertEqual({'foo': 42}, callbacks.responses['1'])
590 self.assertEqual(None, callbacks.exceptions['1'])
591 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
592 self.assertEqual(None, callbacks.exceptions['2'])
Joe Gregorio66f57522011-11-30 11:00:00 -0500593
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500594 def test_execute_request_body(self):
595 batch = BatchHttpRequest()
596
597 batch.add(self.request1)
598 batch.add(self.request2)
599 http = HttpMockSequence([
600 ({'status': '200',
601 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
602 'echo_request_body'),
603 ])
604 try:
605 batch.execute(http)
606 self.fail('Should raise exception')
607 except BatchError, e:
608 boundary, _ = e.content.split(None, 1)
609 self.assertEqual('--', boundary[:2])
610 parts = e.content.split(boundary)
611 self.assertEqual(4, len(parts))
612 self.assertEqual('', parts[0])
613 self.assertEqual('--', parts[3])
614 header = parts[1].splitlines()[1]
615 self.assertEqual('Content-Type: application/http', header)
616
Joe Gregorio654f4a22012-02-09 14:15:44 -0500617 def test_execute_refresh_and_retry_on_401(self):
618 batch = BatchHttpRequest()
619 callbacks = Callbacks()
620 cred_1 = MockCredentials('Foo')
621 cred_2 = MockCredentials('Bar')
622
623 http = HttpMockSequence([
624 ({'status': '200',
625 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
626 BATCH_RESPONSE_WITH_401),
627 ({'status': '200',
628 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
629 BATCH_SINGLE_RESPONSE),
630 ])
631
632 creds_http_1 = HttpMockSequence([])
633 cred_1.authorize(creds_http_1)
634
635 creds_http_2 = HttpMockSequence([])
636 cred_2.authorize(creds_http_2)
637
638 self.request1.http = creds_http_1
639 self.request2.http = creds_http_2
640
641 batch.add(self.request1, callback=callbacks.f)
642 batch.add(self.request2, callback=callbacks.f)
643 batch.execute(http)
644
645 self.assertEqual({'foo': 42}, callbacks.responses['1'])
646 self.assertEqual(None, callbacks.exceptions['1'])
647 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
648 self.assertEqual(None, callbacks.exceptions['2'])
649
650 self.assertEqual(1, cred_1._refreshed)
651 self.assertEqual(0, cred_2._refreshed)
652
653 self.assertEqual(1, cred_1._authorized)
654 self.assertEqual(1, cred_2._authorized)
655
656 self.assertEqual(1, cred_2._applied)
657 self.assertEqual(2, cred_1._applied)
658
659 def test_http_errors_passed_to_callback(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_RESPONSE_WITH_401),
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)
685 batch.execute(http)
686
687 self.assertEqual(None, callbacks.responses['1'])
688 self.assertEqual(401, callbacks.exceptions['1'].resp.status)
Joe Gregorioc752e332012-07-11 14:43:52 -0400689 self.assertEqual(
690 'Authorization Required', callbacks.exceptions['1'].resp.reason)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500691 self.assertEqual({u'baz': u'qux'}, callbacks.responses['2'])
692 self.assertEqual(None, callbacks.exceptions['2'])
693
Joe Gregorio66f57522011-11-30 11:00:00 -0500694 def test_execute_global_callback(self):
Joe Gregorio66f57522011-11-30 11:00:00 -0500695 callbacks = Callbacks()
696 batch = BatchHttpRequest(callback=callbacks.f)
697
698 batch.add(self.request1)
699 batch.add(self.request2)
700 http = HttpMockSequence([
701 ({'status': '200',
702 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
703 BATCH_RESPONSE),
704 ])
705 batch.execute(http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500706 self.assertEqual({'foo': 42}, callbacks.responses['1'])
707 self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500708
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400709 def test_execute_batch_http_error(self):
Joe Gregorio3fb93672012-07-25 11:31:11 -0400710 callbacks = Callbacks()
711 batch = BatchHttpRequest(callback=callbacks.f)
712
713 batch.add(self.request1)
714 batch.add(self.request2)
715 http = HttpMockSequence([
716 ({'status': '200',
717 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
718 BATCH_ERROR_RESPONSE),
719 ])
720 batch.execute(http)
721 self.assertEqual({'foo': 42}, callbacks.responses['1'])
722 expected = ('<HttpError 403 when requesting '
723 'https://www.googleapis.com/someapi/v1/collection/?foo=bar returned '
724 '"Access Not Configured">')
725 self.assertEqual(expected, str(callbacks.exceptions['2']))
Ali Afshar6f11ea12012-02-07 10:32:14 -0500726
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500727if __name__ == '__main__':
728 unittest.main()