blob: 2744d9e5ee332d492f21a534437025459a9aa499 [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
28
Joe Gregorio66f57522011-11-30 11:00:00 -050029from apiclient.errors import BatchError
30from apiclient.http import BatchHttpRequest
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050031from apiclient.http import HttpMockSequence
Joe Gregoriod0bd3882011-11-22 09:49:47 -050032from apiclient.http import HttpRequest
Joe Gregoriod0bd3882011-11-22 09:49:47 -050033from apiclient.http import MediaFileUpload
Joe Gregorio66f57522011-11-30 11:00:00 -050034from apiclient.http import MediaUpload
35from apiclient.http import set_user_agent
36from apiclient.model import JsonModel
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050037
38
Joe Gregoriod0bd3882011-11-22 09:49:47 -050039DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
40
41
42def datafile(filename):
43 return os.path.join(DATA_DIR, filename)
44
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050045class TestUserAgent(unittest.TestCase):
46
47 def test_set_user_agent(self):
48 http = HttpMockSequence([
49 ({'status': '200'}, 'echo_request_headers'),
50 ])
51
52 http = set_user_agent(http, "my_app/5.5")
53 resp, content = http.request("http://example.com")
54 self.assertEqual(content['user-agent'], 'my_app/5.5')
55
56 def test_set_user_agent_nested(self):
57 http = HttpMockSequence([
58 ({'status': '200'}, 'echo_request_headers'),
59 ])
60
61 http = set_user_agent(http, "my_app/5.5")
62 http = set_user_agent(http, "my_library/0.1")
63 resp, content = http.request("http://example.com")
64 self.assertEqual(content['user-agent'], 'my_app/5.5 my_library/0.1')
65
Joe Gregoriod0bd3882011-11-22 09:49:47 -050066 def test_media_file_upload_to_from_json(self):
67 upload = MediaFileUpload(
68 datafile('small.png'), chunksize=500, resumable=True)
69 self.assertEquals('image/png', upload.mimetype())
70 self.assertEquals(190, upload.size())
71 self.assertEquals(True, upload.resumable())
72 self.assertEquals(500, upload.chunksize())
73 self.assertEquals('PNG', upload.getbytes(1, 3))
74
75 json = upload.to_json()
76 new_upload = MediaUpload.new_from_json(json)
77
78 self.assertEquals('image/png', new_upload.mimetype())
79 self.assertEquals(190, new_upload.size())
80 self.assertEquals(True, new_upload.resumable())
81 self.assertEquals(500, new_upload.chunksize())
82 self.assertEquals('PNG', new_upload.getbytes(1, 3))
83
84 def test_http_request_to_from_json(self):
85
86 def _postproc(*kwargs):
87 pass
88
89 http = httplib2.Http()
90 media_upload = MediaFileUpload(
91 datafile('small.png'), chunksize=500, resumable=True)
92 req = HttpRequest(
93 http,
94 _postproc,
95 'http://example.com',
96 method='POST',
97 body='{}',
98 headers={'content-type': 'multipart/related; boundary="---flubber"'},
99 methodId='foo',
100 resumable=media_upload)
101
102 json = req.to_json()
103 new_req = HttpRequest.from_json(json, http, _postproc)
104
Joe Gregorio66f57522011-11-30 11:00:00 -0500105 self.assertEquals(new_req.headers,
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500106 {'content-type':
107 'multipart/related; boundary="---flubber"'})
108 self.assertEquals(new_req.uri, 'http://example.com')
109 self.assertEquals(new_req.body, '{}')
110 self.assertEquals(new_req.http, http)
111 self.assertEquals(new_req.resumable.to_json(), media_upload.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500112
Joe Gregorio66f57522011-11-30 11:00:00 -0500113EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
114Content-Type: application/json
115MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500116Host: www.googleapis.com
117content-length: 2\r\n\r\n{}"""
118
119
120NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
121Content-Type: application/json
122MIME-Version: 1.0
123Host: www.googleapis.com
124content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500125
126
127RESPONSE = """HTTP/1.1 200 OK
128Content-Type application/json
129Content-Length: 14
130ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
131
132
133BATCH_RESPONSE = """--batch_foobarbaz
134Content-Type: application/http
135Content-Transfer-Encoding: binary
136Content-ID: <randomness+1>
137
138HTTP/1.1 200 OK
139Content-Type application/json
140Content-Length: 14
141ETag: "etag/pony"\r\n\r\n{"foo": 42}
142
143--batch_foobarbaz
144Content-Type: application/http
145Content-Transfer-Encoding: binary
146Content-ID: <randomness+2>
147
148HTTP/1.1 200 OK
149Content-Type application/json
150Content-Length: 14
151ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
152--batch_foobarbaz--"""
153
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500154
Joe Gregorio66f57522011-11-30 11:00:00 -0500155class TestBatch(unittest.TestCase):
156
157 def setUp(self):
158 model = JsonModel()
159 self.request1 = HttpRequest(
160 None,
161 model.response,
162 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
163 method='POST',
164 body='{}',
165 headers={'content-type': 'application/json'})
166
167 self.request2 = HttpRequest(
168 None,
169 model.response,
170 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500171 method='GET',
172 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500173 headers={'content-type': 'application/json'})
174
175
176 def test_id_to_from_content_id_header(self):
177 batch = BatchHttpRequest()
178 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
179
180 def test_invalid_content_id_header(self):
181 batch = BatchHttpRequest()
182 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
183 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
184 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
185
186 def test_serialize_request(self):
187 batch = BatchHttpRequest()
188 request = HttpRequest(
189 None,
190 None,
191 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
192 method='POST',
193 body='{}',
194 headers={'content-type': 'application/json'},
195 methodId=None,
196 resumable=None)
197 s = batch._serialize_request(request).splitlines()
198 self.assertEquals(s, EXPECTED.splitlines())
199
Joe Gregoriodd813822012-01-25 10:32:47 -0500200 def test_serialize_request_media_body(self):
201 batch = BatchHttpRequest()
202 f = open(datafile('small.png'))
203 body = f.read()
204 f.close()
205
206 request = HttpRequest(
207 None,
208 None,
209 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
210 method='POST',
211 body=body,
212 headers={'content-type': 'application/json'},
213 methodId=None,
214 resumable=None)
215 s = batch._serialize_request(request).splitlines()
216
217
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500218 def test_serialize_request_no_body(self):
219 batch = BatchHttpRequest()
220 request = HttpRequest(
221 None,
222 None,
223 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
224 method='POST',
225 body='',
226 headers={'content-type': 'application/json'},
227 methodId=None,
228 resumable=None)
229 s = batch._serialize_request(request).splitlines()
230 self.assertEquals(s, NO_BODY_EXPECTED.splitlines())
231
Joe Gregorio66f57522011-11-30 11:00:00 -0500232 def test_deserialize_response(self):
233 batch = BatchHttpRequest()
234 resp, content = batch._deserialize_response(RESPONSE)
235
236 self.assertEquals(resp.status, 200)
237 self.assertEquals(resp.reason, 'OK')
238 self.assertEquals(resp.version, 11)
239 self.assertEquals(content, '{"answer": 42}')
240
241 def test_new_id(self):
242 batch = BatchHttpRequest()
243
244 id_ = batch._new_id()
245 self.assertEquals(id_, '1')
246
247 id_ = batch._new_id()
248 self.assertEquals(id_, '2')
249
250 batch.add(self.request1, request_id='3')
251
252 id_ = batch._new_id()
253 self.assertEquals(id_, '4')
254
255 def test_add(self):
256 batch = BatchHttpRequest()
257 batch.add(self.request1, request_id='1')
258 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
259
260 def test_add_fail_for_resumable(self):
261 batch = BatchHttpRequest()
262
263 upload = MediaFileUpload(
264 datafile('small.png'), chunksize=500, resumable=True)
265 self.request1.resumable = upload
266 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
267
268 def test_execute(self):
269 class Callbacks(object):
270 def __init__(self):
271 self.responses = {}
272
273 def f(self, request_id, response):
274 self.responses[request_id] = response
275
276 batch = BatchHttpRequest()
277 callbacks = Callbacks()
278
279 batch.add(self.request1, callback=callbacks.f)
280 batch.add(self.request2, callback=callbacks.f)
281 http = HttpMockSequence([
282 ({'status': '200',
283 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
284 BATCH_RESPONSE),
285 ])
286 batch.execute(http)
287 self.assertEqual(callbacks.responses['1'], {'foo': 42})
288 self.assertEqual(callbacks.responses['2'], {'baz': 'qux'})
289
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500290 def test_execute_request_body(self):
291 batch = BatchHttpRequest()
292
293 batch.add(self.request1)
294 batch.add(self.request2)
295 http = HttpMockSequence([
296 ({'status': '200',
297 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
298 'echo_request_body'),
299 ])
300 try:
301 batch.execute(http)
302 self.fail('Should raise exception')
303 except BatchError, e:
304 boundary, _ = e.content.split(None, 1)
305 self.assertEqual('--', boundary[:2])
306 parts = e.content.split(boundary)
307 self.assertEqual(4, len(parts))
308 self.assertEqual('', parts[0])
309 self.assertEqual('--', parts[3])
310 header = parts[1].splitlines()[1]
311 self.assertEqual('Content-Type: application/http', header)
312
Joe Gregorio66f57522011-11-30 11:00:00 -0500313 def test_execute_global_callback(self):
314 class Callbacks(object):
315 def __init__(self):
316 self.responses = {}
317
318 def f(self, request_id, response):
319 self.responses[request_id] = response
320
321 callbacks = Callbacks()
322 batch = BatchHttpRequest(callback=callbacks.f)
323
324 batch.add(self.request1)
325 batch.add(self.request2)
326 http = HttpMockSequence([
327 ({'status': '200',
328 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
329 BATCH_RESPONSE),
330 ])
331 batch.execute(http)
332 self.assertEqual(callbacks.responses['1'], {'foo': 42})
333 self.assertEqual(callbacks.responses['2'], {'baz': 'qux'})
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500334
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500335if __name__ == '__main__':
336 unittest.main()