blob: cbbac61431f20a4daa3edf40b16f5038532bc816 [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 Gregoriobd512b52011-12-06 15:39:26 -0500112 self.assertEquals(new_req.multipart_boundary, '---flubber--')
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500113
Joe Gregorio66f57522011-11-30 11:00:00 -0500114EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
115Content-Type: application/json
116MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500117Host: www.googleapis.com
118content-length: 2\r\n\r\n{}"""
119
120
121NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
122Content-Type: application/json
123MIME-Version: 1.0
124Host: www.googleapis.com
125content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500126
127
128RESPONSE = """HTTP/1.1 200 OK
129Content-Type application/json
130Content-Length: 14
131ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
132
133
134BATCH_RESPONSE = """--batch_foobarbaz
135Content-Type: application/http
136Content-Transfer-Encoding: binary
137Content-ID: <randomness+1>
138
139HTTP/1.1 200 OK
140Content-Type application/json
141Content-Length: 14
142ETag: "etag/pony"\r\n\r\n{"foo": 42}
143
144--batch_foobarbaz
145Content-Type: application/http
146Content-Transfer-Encoding: binary
147Content-ID: <randomness+2>
148
149HTTP/1.1 200 OK
150Content-Type application/json
151Content-Length: 14
152ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
153--batch_foobarbaz--"""
154
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500155
Joe Gregorio66f57522011-11-30 11:00:00 -0500156class TestBatch(unittest.TestCase):
157
158 def setUp(self):
159 model = JsonModel()
160 self.request1 = HttpRequest(
161 None,
162 model.response,
163 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
164 method='POST',
165 body='{}',
166 headers={'content-type': 'application/json'})
167
168 self.request2 = HttpRequest(
169 None,
170 model.response,
171 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500172 method='GET',
173 body='',
Joe Gregorio66f57522011-11-30 11:00:00 -0500174 headers={'content-type': 'application/json'})
175
176
177 def test_id_to_from_content_id_header(self):
178 batch = BatchHttpRequest()
179 self.assertEquals('12', batch._header_to_id(batch._id_to_header('12')))
180
181 def test_invalid_content_id_header(self):
182 batch = BatchHttpRequest()
183 self.assertRaises(BatchError, batch._header_to_id, '[foo+x]')
184 self.assertRaises(BatchError, batch._header_to_id, 'foo+1')
185 self.assertRaises(BatchError, batch._header_to_id, '<foo>')
186
187 def test_serialize_request(self):
188 batch = BatchHttpRequest()
189 request = HttpRequest(
190 None,
191 None,
192 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
193 method='POST',
194 body='{}',
195 headers={'content-type': 'application/json'},
196 methodId=None,
197 resumable=None)
198 s = batch._serialize_request(request).splitlines()
199 self.assertEquals(s, EXPECTED.splitlines())
200
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500201 def test_serialize_request_no_body(self):
202 batch = BatchHttpRequest()
203 request = HttpRequest(
204 None,
205 None,
206 'https://www.googleapis.com/someapi/v1/collection/?foo=bar',
207 method='POST',
208 body='',
209 headers={'content-type': 'application/json'},
210 methodId=None,
211 resumable=None)
212 s = batch._serialize_request(request).splitlines()
213 self.assertEquals(s, NO_BODY_EXPECTED.splitlines())
214
Joe Gregorio66f57522011-11-30 11:00:00 -0500215 def test_deserialize_response(self):
216 batch = BatchHttpRequest()
217 resp, content = batch._deserialize_response(RESPONSE)
218
219 self.assertEquals(resp.status, 200)
220 self.assertEquals(resp.reason, 'OK')
221 self.assertEquals(resp.version, 11)
222 self.assertEquals(content, '{"answer": 42}')
223
224 def test_new_id(self):
225 batch = BatchHttpRequest()
226
227 id_ = batch._new_id()
228 self.assertEquals(id_, '1')
229
230 id_ = batch._new_id()
231 self.assertEquals(id_, '2')
232
233 batch.add(self.request1, request_id='3')
234
235 id_ = batch._new_id()
236 self.assertEquals(id_, '4')
237
238 def test_add(self):
239 batch = BatchHttpRequest()
240 batch.add(self.request1, request_id='1')
241 self.assertRaises(KeyError, batch.add, self.request1, request_id='1')
242
243 def test_add_fail_for_resumable(self):
244 batch = BatchHttpRequest()
245
246 upload = MediaFileUpload(
247 datafile('small.png'), chunksize=500, resumable=True)
248 self.request1.resumable = upload
249 self.assertRaises(BatchError, batch.add, self.request1, request_id='1')
250
251 def test_execute(self):
252 class Callbacks(object):
253 def __init__(self):
254 self.responses = {}
255
256 def f(self, request_id, response):
257 self.responses[request_id] = response
258
259 batch = BatchHttpRequest()
260 callbacks = Callbacks()
261
262 batch.add(self.request1, callback=callbacks.f)
263 batch.add(self.request2, callback=callbacks.f)
264 http = HttpMockSequence([
265 ({'status': '200',
266 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
267 BATCH_RESPONSE),
268 ])
269 batch.execute(http)
270 self.assertEqual(callbacks.responses['1'], {'foo': 42})
271 self.assertEqual(callbacks.responses['2'], {'baz': 'qux'})
272
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500273 def test_execute_request_body(self):
274 batch = BatchHttpRequest()
275
276 batch.add(self.request1)
277 batch.add(self.request2)
278 http = HttpMockSequence([
279 ({'status': '200',
280 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
281 'echo_request_body'),
282 ])
283 try:
284 batch.execute(http)
285 self.fail('Should raise exception')
286 except BatchError, e:
287 boundary, _ = e.content.split(None, 1)
288 self.assertEqual('--', boundary[:2])
289 parts = e.content.split(boundary)
290 self.assertEqual(4, len(parts))
291 self.assertEqual('', parts[0])
292 self.assertEqual('--', parts[3])
293 header = parts[1].splitlines()[1]
294 self.assertEqual('Content-Type: application/http', header)
295
Joe Gregorio66f57522011-11-30 11:00:00 -0500296 def test_execute_global_callback(self):
297 class Callbacks(object):
298 def __init__(self):
299 self.responses = {}
300
301 def f(self, request_id, response):
302 self.responses[request_id] = response
303
304 callbacks = Callbacks()
305 batch = BatchHttpRequest(callback=callbacks.f)
306
307 batch.add(self.request1)
308 batch.add(self.request2)
309 http = HttpMockSequence([
310 ({'status': '200',
311 'content-type': 'multipart/mixed; boundary="batch_foobarbaz"'},
312 BATCH_RESPONSE),
313 ])
314 batch.execute(http)
315 self.assertEqual(callbacks.responses['1'], {'foo': 42})
316 self.assertEqual(callbacks.responses['2'], {'baz': 'qux'})
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500317
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500318if __name__ == '__main__':
319 unittest.main()