blob: 499edda427abea1a47b78c5faaf08ddeb28d530a [file] [log] [blame]
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001#!/usr/bin/python2.4
Joe Gregoriof863f7a2011-02-24 03:24:44 -05002# -*- coding: utf-8 -*-
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04003#
Joe Gregorio6d5e94f2010-08-25 23:49:30 -04004# Copyright 2010 Google Inc.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040018
19"""Discovery document tests
20
21Unit tests for objects created from discovery documents.
22"""
23
24__author__ = 'jcgregorio@google.com (Joe Gregorio)'
25
Daniel Hermesc2113242013-02-27 10:16:13 -080026import copy
Joe Gregoriodc106fc2012-11-20 14:30:14 -050027import datetime
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040028import httplib2
29import os
Joe Gregoriodc106fc2012-11-20 14:30:14 -050030import pickle
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040031import sys
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040032import unittest
Joe Gregorio00cf1d92010-09-27 09:22:03 -040033import urlparse
Joe Gregorio910b9b12012-06-12 09:36:30 -040034import StringIO
35
Joe Gregorioa98733f2011-09-16 10:12:28 -040036
ade@google.comc5eb46f2010-09-27 23:35:39 +010037try:
Daniel Hermesc2113242013-02-27 10:16:13 -080038 from urlparse import parse_qs
ade@google.comc5eb46f2010-09-27 23:35:39 +010039except ImportError:
Daniel Hermesc2113242013-02-27 10:16:13 -080040 from cgi import parse_qs
Joe Gregorio00cf1d92010-09-27 09:22:03 -040041
Joe Gregoriodc106fc2012-11-20 14:30:14 -050042
Daniel Hermesc2113242013-02-27 10:16:13 -080043from apiclient.discovery import _fix_up_media_upload
44from apiclient.discovery import _fix_up_method_description
45from apiclient.discovery import _fix_up_parameters
Joe Gregoriodc106fc2012-11-20 14:30:14 -050046from apiclient.discovery import build
47from apiclient.discovery import build_from_document
48from apiclient.discovery import DISCOVERY_URI
49from apiclient.discovery import key2param
Daniel Hermesc2113242013-02-27 10:16:13 -080050from apiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
Daniel Hermes954e1242013-02-28 09:28:37 -080051from apiclient.discovery import ResourceMethodParameters
Daniel Hermesc2113242013-02-27 10:16:13 -080052from apiclient.discovery import STACK_QUERY_PARAMETERS
53from apiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Joe Gregorioc0e0fe92011-03-04 16:16:55 -050054from apiclient.errors import HttpError
Joe Gregorio49396552011-03-08 10:39:00 -050055from apiclient.errors import InvalidJsonError
Joe Gregoriofdf7c802011-06-30 12:33:38 -040056from apiclient.errors import MediaUploadSizeError
Joe Gregoriod0bd3882011-11-22 09:49:47 -050057from apiclient.errors import ResumableUploadError
Joe Gregoriofdf7c802011-06-30 12:33:38 -040058from apiclient.errors import UnacceptableMimeTypeError
Joe Gregoriod0bd3882011-11-22 09:49:47 -050059from apiclient.http import HttpMock
60from apiclient.http import HttpMockSequence
61from apiclient.http import MediaFileUpload
Joe Gregorio910b9b12012-06-12 09:36:30 -040062from apiclient.http import MediaIoBaseUpload
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040063from apiclient.http import MediaUpload
Joe Gregoriod0bd3882011-11-22 09:49:47 -050064from apiclient.http import MediaUploadProgress
65from apiclient.http import tunnel_patch
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080066from oauth2client import GOOGLE_TOKEN_URI
Joe Gregorio79daca02013-03-29 16:25:52 -040067from oauth2client import util
Joe Gregorio910b9b12012-06-12 09:36:30 -040068from oauth2client.anyjson import simplejson
Joe Gregoriodc106fc2012-11-20 14:30:14 -050069from oauth2client.client import OAuth2Credentials
Joe Gregorio79daca02013-03-29 16:25:52 -040070
Joe Gregoriodc106fc2012-11-20 14:30:14 -050071import uritemplate
72
Joe Gregoriocb8103d2011-02-11 23:20:52 -050073
74DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
75
Joe Gregorio79daca02013-03-29 16:25:52 -040076util.positional_parameters_enforcement = util.POSITIONAL_EXCEPTION
Joe Gregorio32f048f2012-08-27 16:31:27 -040077
Joe Gregorioa98733f2011-09-16 10:12:28 -040078
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050079def assertUrisEqual(testcase, expected, actual):
80 """Test that URIs are the same, up to reordering of query parameters."""
81 expected = urlparse.urlparse(expected)
82 actual = urlparse.urlparse(actual)
83 testcase.assertEqual(expected.scheme, actual.scheme)
84 testcase.assertEqual(expected.netloc, actual.netloc)
85 testcase.assertEqual(expected.path, actual.path)
86 testcase.assertEqual(expected.params, actual.params)
87 testcase.assertEqual(expected.fragment, actual.fragment)
88 expected_query = parse_qs(expected.query)
89 actual_query = parse_qs(actual.query)
90 for name in expected_query.keys():
91 testcase.assertEqual(expected_query[name], actual_query[name])
92 for name in actual_query.keys():
93 testcase.assertEqual(expected_query[name], actual_query[name])
94
95
Joe Gregoriocb8103d2011-02-11 23:20:52 -050096def datafile(filename):
97 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040098
99
Joe Gregorio504a17f2012-12-07 14:14:26 -0500100class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800101
Joe Gregorio504a17f2012-12-07 14:14:26 -0500102 def test_retries(self):
103 # Merely loading apiclient.discovery should set the RETRIES to 1.
104 self.assertEqual(1, httplib2.RETRIES)
105
106
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400107class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800108
109 def setUp(self):
110 with open(datafile('zoo.json'), 'r') as fh:
111 self.zoo_root_desc = simplejson.loads(fh.read())
112 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800113 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
114 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Daniel Hermesc2113242013-02-27 10:16:13 -0800115
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400116 def test_key2param(self):
117 self.assertEqual('max_results', key2param('max-results'))
118 self.assertEqual('x007_bond', key2param('007-bond'))
119
Daniel Hermesc2113242013-02-27 10:16:13 -0800120 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
121 self.assertEqual(method_desc['httpMethod'], http_method)
122
123 method_desc_copy = copy.deepcopy(method_desc)
124 self.assertEqual(method_desc, method_desc_copy)
125
126 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
127
128 self.assertNotEqual(method_desc, method_desc_copy)
129
130 for param_name in STACK_QUERY_PARAMETERS:
131 self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
132 parameters[param_name])
133
134 for param_name, value in root_desc.get('parameters', {}).iteritems():
135 self.assertEqual(value, parameters[param_name])
136
137 return parameters
138
139 def test_fix_up_parameters_get(self):
140 parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
141 'GET', self.zoo_root_desc)
142 # Since http_method is 'GET'
143 self.assertFalse(parameters.has_key('body'))
144
145 def test_fix_up_parameters_insert(self):
146 parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
147 'POST', self.zoo_root_desc)
148 body = {
149 'description': 'The request body.',
150 'type': 'object',
151 'required': True,
152 '$ref': 'Animal',
153 }
154 self.assertEqual(parameters['body'], body)
155
156 def test_fix_up_parameters_check_body(self):
157 dummy_root_desc = {}
158 no_payload_http_method = 'DELETE'
159 with_payload_http_method = 'PUT'
160
161 invalid_method_desc = {'response': 'Who cares'}
162 valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
163
164 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
165 no_payload_http_method)
166 self.assertFalse(parameters.has_key('body'))
167
168 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
169 no_payload_http_method)
170 self.assertFalse(parameters.has_key('body'))
171
172 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
173 with_payload_http_method)
174 self.assertFalse(parameters.has_key('body'))
175
176 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
177 with_payload_http_method)
178 body = {
179 'description': 'The request body.',
180 'type': 'object',
181 'required': True,
182 'key1': 'value1',
183 'key2': 'value2',
184 }
185 self.assertEqual(parameters['body'], body)
186
187 def _base_fix_up_method_description_test(
188 self, method_desc, initial_parameters, final_parameters,
189 final_accept, final_max_size, final_media_path_url):
190 fake_root_desc = {'rootUrl': 'http://root/',
191 'servicePath': 'fake/'}
192 fake_path_url = 'fake-path/'
193
194 accept, max_size, media_path_url = _fix_up_media_upload(
195 method_desc, fake_root_desc, fake_path_url, initial_parameters)
196 self.assertEqual(accept, final_accept)
197 self.assertEqual(max_size, final_max_size)
198 self.assertEqual(media_path_url, final_media_path_url)
199 self.assertEqual(initial_parameters, final_parameters)
200
201 def test_fix_up_media_upload_no_initial_invalid(self):
202 invalid_method_desc = {'response': 'Who cares'}
203 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
204 [], 0, None)
205
206 def test_fix_up_media_upload_no_initial_valid_minimal(self):
207 valid_method_desc = {'mediaUpload': {'accept': []}}
208 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
209 self._base_fix_up_method_description_test(
210 valid_method_desc, {}, final_parameters, [], 0,
211 'http://root/upload/fake/fake-path/')
212
213 def test_fix_up_media_upload_no_initial_valid_full(self):
214 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
215 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
216 ten_gb = 10 * 2**30
217 self._base_fix_up_method_description_test(
218 valid_method_desc, {}, final_parameters, ['*/*'],
219 ten_gb, 'http://root/upload/fake/fake-path/')
220
221 def test_fix_up_media_upload_with_initial_invalid(self):
222 invalid_method_desc = {'response': 'Who cares'}
223 initial_parameters = {'body': {}}
224 self._base_fix_up_method_description_test(
225 invalid_method_desc, initial_parameters,
226 initial_parameters, [], 0, None)
227
228 def test_fix_up_media_upload_with_initial_valid_minimal(self):
229 valid_method_desc = {'mediaUpload': {'accept': []}}
230 initial_parameters = {'body': {}}
231 final_parameters = {'body': {'required': False},
232 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
233 self._base_fix_up_method_description_test(
234 valid_method_desc, initial_parameters, final_parameters, [], 0,
235 'http://root/upload/fake/fake-path/')
236
237 def test_fix_up_media_upload_with_initial_valid_full(self):
238 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
239 initial_parameters = {'body': {}}
240 final_parameters = {'body': {'required': False},
241 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
242 ten_gb = 10 * 2**30
243 self._base_fix_up_method_description_test(
244 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
245 ten_gb, 'http://root/upload/fake/fake-path/')
246
247 def test_fix_up_method_description_get(self):
248 result = _fix_up_method_description(self.zoo_get_method_desc,
249 self.zoo_root_desc)
250 path_url = 'query'
251 http_method = 'GET'
252 method_id = 'bigquery.query'
253 accept = []
254 max_size = 0L
255 media_path_url = None
256 self.assertEqual(result, (path_url, http_method, method_id, accept,
257 max_size, media_path_url))
258
259 def test_fix_up_method_description_insert(self):
260 result = _fix_up_method_description(self.zoo_insert_method_desc,
261 self.zoo_root_desc)
262 path_url = 'animals'
263 http_method = 'POST'
264 method_id = 'zoo.animals.insert'
265 accept = ['image/png']
266 max_size = 1024L
267 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
268 self.assertEqual(result, (path_url, http_method, method_id, accept,
269 max_size, media_path_url))
270
Daniel Hermes954e1242013-02-28 09:28:37 -0800271 def test_ResourceMethodParameters_zoo_get(self):
272 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
273
274 param_types = {'a': 'any',
275 'b': 'boolean',
276 'e': 'string',
277 'er': 'string',
278 'i': 'integer',
279 'n': 'number',
280 'o': 'object',
281 'q': 'string',
282 'rr': 'string'}
283 keys = param_types.keys()
284 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
285 self.assertEqual(parameters.required_params, [])
286 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
287 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
288 self.assertEqual(sorted(parameters.query_params),
289 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
290 self.assertEqual(parameters.path_params, set())
291 self.assertEqual(parameters.param_types, param_types)
292 enum_params = {'e': ['foo', 'bar'],
293 'er': ['one', 'two', 'three']}
294 self.assertEqual(parameters.enum_params, enum_params)
295
296 def test_ResourceMethodParameters_zoo_animals_patch(self):
297 method_desc = self.zoo_animals_resource['methods']['patch']
298 parameters = ResourceMethodParameters(method_desc)
299
300 param_types = {'name': 'string'}
301 keys = param_types.keys()
302 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
303 self.assertEqual(parameters.required_params, ['name'])
304 self.assertEqual(parameters.repeated_params, [])
305 self.assertEqual(parameters.pattern_params, {})
306 self.assertEqual(parameters.query_params, [])
307 self.assertEqual(parameters.path_params, set(['name']))
308 self.assertEqual(parameters.param_types, param_types)
309 self.assertEqual(parameters.enum_params, {})
310
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400311
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500312class DiscoveryErrors(unittest.TestCase):
313
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400314 def test_tests_should_be_run_with_strict_positional_enforcement(self):
315 try:
316 plus = build('plus', 'v1', None)
317 self.fail("should have raised a TypeError exception over missing http=.")
318 except TypeError:
319 pass
320
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500321 def test_failed_to_parse_discovery_json(self):
322 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
323 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400324 plus = build('plus', 'v1', http=self.http)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500325 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500326 except InvalidJsonError:
327 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500328
329
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100330class DiscoveryFromDocument(unittest.TestCase):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400331
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100332 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400333 discovery = open(datafile('plus.json')).read()
Joe Gregorio7b70f432011-11-09 10:18:51 -0500334 plus = build_from_document(discovery, base="https://www.googleapis.com/")
335 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500336 self.assertTrue(hasattr(plus, 'activities'))
337
338 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400339 discovery = open(datafile('plus.json')).read()
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500340 discovery = simplejson.loads(discovery)
341 plus = build_from_document(discovery, base="https://www.googleapis.com/")
342 self.assertTrue(plus is not None)
343 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400344
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100345 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400346 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400347
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100348 base = "https://www.example.com/"
Joe Gregorio7b70f432011-11-09 10:18:51 -0500349 plus = build_from_document(discovery, base=base)
Joe Gregorioa2838152012-07-16 11:52:17 -0400350 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100351
352
Joe Gregorioa98733f2011-09-16 10:12:28 -0400353class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400354 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700355 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400356
Joe Gregorio583d9e42011-09-16 15:54:15 -0400357 def tearDown(self):
358 os.environ = self.old_environ
359
360 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400361 # build() will raise an HttpError on a 400, use this to pick the request uri
362 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400363 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400364 try:
365 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400366 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400367 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400368 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400369 discoveryServiceUrl='http://example.com')
370 self.fail('Should have raised an exception.')
371 except HttpError, e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400372 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400373
Joe Gregorio583d9e42011-09-16 15:54:15 -0400374 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400375 # build() will raise an HttpError on a 400, use this to pick the request uri
376 # out of the raised exception.
377 try:
378 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400379 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400380 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400381 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400382 discoveryServiceUrl='http://example.com')
383 self.fail('Should have raised an exception.')
384 except HttpError, e:
385 self.assertEqual(e.uri, 'http://example.com')
386
387
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400388class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400389
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400390 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500391 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400392 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400393
394 # Missing required parameters
395 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500396 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400397 self.fail()
398 except TypeError, e:
399 self.assertTrue('Missing' in str(e))
400
Joe Gregorio2467afa2012-06-20 12:21:25 -0400401 # Missing required parameters even if supplied as None.
402 try:
403 plus.activities().list(collection=None, userId=None)
404 self.fail()
405 except TypeError, e:
406 self.assertTrue('Missing' in str(e))
407
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400408 # Parameter doesn't match regex
409 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500410 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400411 self.fail()
412 except TypeError, e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500413 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400414
415 # Unexpected parameter
416 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500417 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400418 self.fail()
419 except TypeError, e:
420 self.assertTrue('unexpected' in str(e))
421
Joe Gregoriobee86832011-02-22 10:00:19 -0500422 def _check_query_types(self, request):
423 parsed = urlparse.urlparse(request.uri)
424 q = parse_qs(parsed[4])
425 self.assertEqual(q['q'], ['foo'])
426 self.assertEqual(q['i'], ['1'])
427 self.assertEqual(q['n'], ['1.0'])
428 self.assertEqual(q['b'], ['false'])
429 self.assertEqual(q['a'], ['[1, 2, 3]'])
430 self.assertEqual(q['o'], ['{\'a\': 1}'])
431 self.assertEqual(q['e'], ['bar'])
432
433 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400434 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400435 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500436
Joe Gregorioa98733f2011-09-16 10:12:28 -0400437 request = zoo.query(
438 q="foo", i=1.0, n=1.0, b=0, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500439 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400440 request = zoo.query(
441 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500442 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500443
Joe Gregorioa98733f2011-09-16 10:12:28 -0400444 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800445 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar', er='two')
Joe Gregorio6804c7a2011-11-18 14:30:32 -0500446
447 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800448 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
449 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500450 self._check_query_types(request)
451
Craig Citro1e742822012-03-01 12:59:22 -0800452 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500453 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800454
Joe Gregorio13217952011-02-22 15:37:38 -0500455 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400456 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400457 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400458 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500459
Joe Gregorioca876e42011-02-22 19:39:42 -0500460 parsed = urlparse.urlparse(request.uri)
461 q = parse_qs(parsed[4])
462 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400463 self.assertEqual(q['fields'], ['description'])
464
Joe Gregorio2467afa2012-06-20 12:21:25 -0400465 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400466 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400467 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400468 request = zoo.query(trace=None, fields='description')
469
470 parsed = urlparse.urlparse(request.uri)
471 q = parse_qs(parsed[4])
472 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400473
Joe Gregorioe08a1662011-12-07 09:48:22 -0500474 def test_model_added_query_parameters(self):
475 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400476 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500477 request = zoo.animals().get(name='Lion')
478
479 parsed = urlparse.urlparse(request.uri)
480 q = parse_qs(parsed[4])
481 self.assertEqual(q['alt'], ['json'])
482 self.assertEqual(request.headers['accept'], 'application/json')
483
484 def test_fallback_to_raw_model(self):
485 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400486 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500487 request = zoo.animals().getmedia(name='Lion')
488
489 parsed = urlparse.urlparse(request.uri)
490 q = parse_qs(parsed[4])
491 self.assertTrue('alt' not in q)
492 self.assertEqual(request.headers['accept'], '*/*')
493
Joe Gregoriof4153422011-03-18 22:45:18 -0400494 def test_patch(self):
495 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400496 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400497 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
498
499 self.assertEqual(request.method, 'PATCH')
500
501 def test_tunnel_patch(self):
502 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400503 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400504 ({'status': '200'}, 'echo_request_headers_as_json'),
505 ])
506 http = tunnel_patch(http)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400507 zoo = build('zoo', 'v1', http=http)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400508 resp = zoo.animals().patch(
509 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400510
511 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500512
Joe Gregorio7b70f432011-11-09 10:18:51 -0500513 def test_plus_resources(self):
514 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400515 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500516 self.assertTrue(getattr(plus, 'activities'))
517 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400518
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400519 def test_full_featured(self):
520 # Zoo should exercise all discovery facets
521 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500522 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400523 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400524 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500525
Joe Gregoriof4153422011-03-18 22:45:18 -0400526 request = zoo.animals().list(name='bat', projection="full")
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400527 parsed = urlparse.urlparse(request.uri)
528 q = parse_qs(parsed[4])
529 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400530 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400531
Joe Gregorio3fada332011-01-07 17:07:45 -0500532 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500533 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400534 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500535 self.assertTrue(getattr(zoo, 'animals'))
536 request = zoo.my().favorites().list(max_results="5")
537 parsed = urlparse.urlparse(request.uri)
538 q = parse_qs(parsed[4])
539 self.assertEqual(q['max-results'], ['5'])
540
Joe Gregoriod92897c2011-07-07 11:44:56 -0400541 def test_methods_with_reserved_names(self):
542 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400543 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400544 self.assertTrue(getattr(zoo, 'animals'))
545 request = zoo.global_().print_().assert_(max_results="5")
546 parsed = urlparse.urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400547 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400548
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500549 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500550 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400551 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500552 self.assertTrue(getattr(zoo, 'query'))
553 request = zoo.query(q="foo")
554 parsed = urlparse.urlparse(request.uri)
555 q = parse_qs(parsed[4])
556 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400557
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400558 def test_simple_media_uploads(self):
559 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400560 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400561 doc = getattr(zoo.animals().insert, '__doc__')
562 self.assertTrue('media_body' in doc)
563
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400564 def test_simple_media_upload_no_max_size_provided(self):
565 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400566 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400567 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
568 self.assertEquals('image/png', request.headers['content-type'])
569 self.assertEquals('PNG', request.body[1:4])
570
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400571 def test_simple_media_raise_correct_exceptions(self):
572 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400573 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400574
575 try:
576 zoo.animals().insert(media_body=datafile('smiley.png'))
577 self.fail("should throw exception if media is too large.")
578 except MediaUploadSizeError:
579 pass
580
581 try:
582 zoo.animals().insert(media_body=datafile('small.jpg'))
583 self.fail("should throw exception if mimetype is unacceptable.")
584 except UnacceptableMimeTypeError:
585 pass
586
587 def test_simple_media_good_upload(self):
588 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400589 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400590
591 request = zoo.animals().insert(media_body=datafile('small.png'))
592 self.assertEquals('image/png', request.headers['content-type'])
593 self.assertEquals('PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500594 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400595 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500596 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400597
598 def test_multipart_media_raise_correct_exceptions(self):
599 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400600 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400601
602 try:
603 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
604 self.fail("should throw exception if media is too large.")
605 except MediaUploadSizeError:
606 pass
607
608 try:
609 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
610 self.fail("should throw exception if mimetype is unacceptable.")
611 except UnacceptableMimeTypeError:
612 pass
613
614 def test_multipart_media_good_upload(self):
615 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400616 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400617
618 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400619 self.assertTrue(request.headers['content-type'].startswith(
620 'multipart/related'))
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400621 self.assertEquals('--==', request.body[0:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500622 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400623 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500624 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400625
626 def test_media_capable_method_without_media(self):
627 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400628 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400629
630 request = zoo.animals().insert(body={})
631 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400632
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500633 def test_resumable_multipart_media_good_upload(self):
634 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400635 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500636
637 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
638 request = zoo.animals().insert(media_body=media_upload, body={})
639 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500640 'application/json'))
641 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500642 self.assertEquals(media_upload, request.resumable)
643
644 self.assertEquals('image/png', request.resumable.mimetype())
645
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500646 self.assertNotEquals(request.body, None)
647 self.assertEquals(request.resumable_uri, None)
648
649 http = HttpMockSequence([
650 ({'status': '200',
651 'location': 'http://upload.example.com'}, ''),
652 ({'status': '308',
653 'location': 'http://upload.example.com/2',
654 'range': '0-12'}, ''),
655 ({'status': '308',
656 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500657 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500658 ({'status': '200'}, '{"foo": "bar"}'),
659 ])
660
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400661 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500662 self.assertEquals(None, body)
663 self.assertTrue(isinstance(status, MediaUploadProgress))
664 self.assertEquals(13, status.resumable_progress)
665
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500666 # Two requests should have been made and the resumable_uri should have been
667 # updated for each one.
668 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
669
670 self.assertEquals(media_upload, request.resumable)
671 self.assertEquals(13, request.resumable_progress)
672
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400673 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500674 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500675 self.assertEquals(media_upload.size()-1, request.resumable_progress)
676 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500677
678 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400679 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500680 self.assertEquals(body, {"foo": "bar"})
681 self.assertEquals(status, None)
682
683
684 def test_resumable_media_good_upload(self):
685 """Not a multipart upload."""
686 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400687 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500688
689 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
690 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500691 self.assertEquals(media_upload, request.resumable)
692
693 self.assertEquals('image/png', request.resumable.mimetype())
694
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500695 self.assertEquals(request.body, None)
696 self.assertEquals(request.resumable_uri, None)
697
698 http = HttpMockSequence([
699 ({'status': '200',
700 'location': 'http://upload.example.com'}, ''),
701 ({'status': '308',
702 'location': 'http://upload.example.com/2',
703 'range': '0-12'}, ''),
704 ({'status': '308',
705 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500706 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500707 ({'status': '200'}, '{"foo": "bar"}'),
708 ])
709
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400710 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500711 self.assertEquals(None, body)
712 self.assertTrue(isinstance(status, MediaUploadProgress))
713 self.assertEquals(13, status.resumable_progress)
714
715 # Two requests should have been made and the resumable_uri should have been
716 # updated for each one.
717 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
718
719 self.assertEquals(media_upload, request.resumable)
720 self.assertEquals(13, request.resumable_progress)
721
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400722 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500723 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500724 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500725 self.assertEquals(request.body, None)
726
727 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400728 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500729 self.assertEquals(body, {"foo": "bar"})
730 self.assertEquals(status, None)
731
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500732 def test_resumable_media_good_upload_from_execute(self):
733 """Not a multipart upload."""
734 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400735 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500736
737 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
738 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500739 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400740 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500741 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500742
743 http = HttpMockSequence([
744 ({'status': '200',
745 'location': 'http://upload.example.com'}, ''),
746 ({'status': '308',
747 'location': 'http://upload.example.com/2',
748 'range': '0-12'}, ''),
749 ({'status': '308',
750 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500751 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500752 ({'status': '200'}, '{"foo": "bar"}'),
753 ])
754
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400755 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500756 self.assertEquals(body, {"foo": "bar"})
757
758 def test_resumable_media_fail_unknown_response_code_first_request(self):
759 """Not a multipart upload."""
760 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400761 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500762
763 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
764 request = zoo.animals().insert(media_body=media_upload, body=None)
765
766 http = HttpMockSequence([
767 ({'status': '400',
768 'location': 'http://upload.example.com'}, ''),
769 ])
770
Joe Gregoriobaf04802013-03-01 12:27:06 -0500771 try:
772 request.execute(http=http)
773 self.fail('Should have raised ResumableUploadError.')
774 except ResumableUploadError, e:
775 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500776
777 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
778 """Not a multipart upload."""
779 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400780 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500781
782 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
783 request = zoo.animals().insert(media_body=media_upload, body=None)
784
785 http = HttpMockSequence([
786 ({'status': '200',
787 'location': 'http://upload.example.com'}, ''),
788 ({'status': '400'}, ''),
789 ])
790
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400791 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400792 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500793
Joe Gregorio910b9b12012-06-12 09:36:30 -0400794 http = HttpMockSequence([
795 ({'status': '308',
796 'range': '0-5'}, ''),
797 ({'status': '308',
798 'range': '0-6'}, ''),
799 ])
800
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400801 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400802 self.assertEquals(status.resumable_progress, 7,
803 'Should have first checked length and then tried to PUT more.')
804 self.assertFalse(request._in_error_state)
805
806 # Put it back in an error state.
807 http = HttpMockSequence([
808 ({'status': '400'}, ''),
809 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400810 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400811 self.assertTrue(request._in_error_state)
812
813 # Pretend the last request that 400'd actually succeeded.
814 http = HttpMockSequence([
815 ({'status': '200'}, '{"foo": "bar"}'),
816 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400817 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400818 self.assertEqual(body, {'foo': 'bar'})
819
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400820 def test_media_io_base_stream_unlimited_chunksize_resume(self):
821 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
822 zoo = build('zoo', 'v1', http=self.http)
823
824 try:
825 import io
826
827 # Set up a seekable stream and try to upload in single chunk.
828 fd = io.BytesIO('01234"56789"')
829 media_upload = MediaIoBaseUpload(
830 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
831
832 request = zoo.animals().insert(media_body=media_upload, body=None)
833
834 # The single chunk fails, restart at the right point.
835 http = HttpMockSequence([
836 ({'status': '200',
837 'location': 'http://upload.example.com'}, ''),
838 ({'status': '308',
839 'location': 'http://upload.example.com/2',
840 'range': '0-4'}, ''),
841 ({'status': '200'}, 'echo_request_body'),
842 ])
843
844 body = request.execute(http=http)
845 self.assertEqual('56789', body)
846
847 except ImportError:
848 pass
849
Joe Gregorio5c120db2012-08-23 09:13:55 -0400850
851 def test_media_io_base_stream_chunksize_resume(self):
852 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
853 zoo = build('zoo', 'v1', http=self.http)
854
855 try:
856 import io
857
858 # Set up a seekable stream and try to upload in chunks.
859 fd = io.BytesIO('0123456789')
860 media_upload = MediaIoBaseUpload(
861 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
862
863 request = zoo.animals().insert(media_body=media_upload, body=None)
864
865 # The single chunk fails, pull the content sent out of the exception.
866 http = HttpMockSequence([
867 ({'status': '200',
868 'location': 'http://upload.example.com'}, ''),
869 ({'status': '400'}, 'echo_request_body'),
870 ])
871
872 try:
873 body = request.execute(http=http)
874 except HttpError, e:
875 self.assertEqual('01234', e.content)
876
877 except ImportError:
878 pass
879
880
Joe Gregorio910b9b12012-06-12 09:36:30 -0400881 def test_resumable_media_handle_uploads_of_unknown_size(self):
882 http = HttpMockSequence([
883 ({'status': '200',
884 'location': 'http://upload.example.com'}, ''),
885 ({'status': '200'}, 'echo_request_headers_as_json'),
886 ])
887
888 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400889 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400890
Joe Gregorio910b9b12012-06-12 09:36:30 -0400891 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400892 class IoBaseUnknownLength(MediaUpload):
893 def chunksize(self):
894 return 10
895
896 def mimetype(self):
897 return 'image/png'
898
899 def size(self):
900 return None
901
902 def resumable(self):
903 return True
904
905 def getbytes(self, begin, length):
906 return '0123456789'
907
908 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400909
910 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400911 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400912 self.assertEqual(body, {
913 'Content-Range': 'bytes 0-9/*',
914 'Content-Length': '10',
915 })
Joe Gregorio44454e42012-06-15 08:38:53 -0400916
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400917 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
918 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
919 zoo = build('zoo', 'v1', http=self.http)
920
921 class IoBaseHasStream(MediaUpload):
922 def chunksize(self):
923 return 10
924
925 def mimetype(self):
926 return 'image/png'
927
928 def size(self):
929 return None
930
931 def resumable(self):
932 return True
933
934 def getbytes(self, begin, length):
935 return '0123456789'
936
937 def has_stream(self):
938 return True
939
940 def stream(self):
941 raise NotImplementedError()
942
943 upload = IoBaseHasStream()
944
945 orig_version = sys.version_info
946 sys.version_info = (2, 5, 5, 'final', 0)
947
948 request = zoo.animals().insert(media_body=upload, body=None)
949
950 http = HttpMockSequence([
951 ({'status': '200',
952 'location': 'http://upload.example.com'}, ''),
953 ({'status': '200'}, 'echo_request_headers_as_json'),
954 ])
955
956 # This should not raise an exception because stream() shouldn't be called.
957 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400958 self.assertEqual(body, {
959 'Content-Range': 'bytes 0-9/*',
960 'Content-Length': '10'
961 })
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400962
963 sys.version_info = (2, 6, 5, 'final', 0)
964
965 request = zoo.animals().insert(media_body=upload, body=None)
966
967 # This should raise an exception because stream() will be called.
968 http = HttpMockSequence([
969 ({'status': '200',
970 'location': 'http://upload.example.com'}, ''),
971 ({'status': '200'}, 'echo_request_headers_as_json'),
972 ])
973
974 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
975
976 sys.version_info = orig_version
977
Joe Gregorio44454e42012-06-15 08:38:53 -0400978 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
979 http = HttpMockSequence([
980 ({'status': '200',
981 'location': 'http://upload.example.com'}, ''),
982 ({'status': '200'}, 'echo_request_headers_as_json'),
983 ])
984
985 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400986 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -0400987
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400988 fd = StringIO.StringIO('data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -0400989
990 # Create an upload that doesn't know the full size of the media.
991 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400992 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -0400993
994 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400995 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400996 self.assertEqual(body, {
997 'Content-Range': 'bytes 0-13/14',
998 'Content-Length': '14',
999 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001000
1001 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1002 http = HttpMockSequence([
1003 ({'status': '200',
1004 'location': 'http://upload.example.com'}, ''),
1005 ({'status': '400'}, ''),
1006 ])
1007
1008 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001009 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001010
1011 # Create an upload that doesn't know the full size of the media.
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001012 fd = StringIO.StringIO('data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001013
1014 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001015 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001016
1017 request = zoo.animals().insert(media_body=upload, body=None)
1018
1019 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001020 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001021
1022 http = HttpMockSequence([
1023 ({'status': '400',
1024 'range': '0-5'}, 'echo_request_headers_as_json'),
1025 ])
1026 try:
1027 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001028 request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001029 except HttpError, e:
1030 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001031 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001032 'content-length': '0'
1033 }
1034 self.assertEqual(expected, simplejson.loads(e.content),
1035 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001036
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001037 def test_pickle(self):
1038 sorted_resource_keys = ['_baseUrl',
1039 '_developerKey',
1040 '_dynamic_attrs',
1041 '_http',
1042 '_model',
1043 '_requestBuilder',
1044 '_resourceDesc',
1045 '_rootDesc',
1046 '_schema',
1047 'animals',
1048 'global_',
1049 'load',
1050 'loadNoTemplate',
1051 'my',
1052 'query',
1053 'scopedAnimals']
1054
1055 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1056 zoo = build('zoo', 'v1', http=http)
1057 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1058
1059 pickled_zoo = pickle.dumps(zoo)
1060 new_zoo = pickle.loads(pickled_zoo)
1061 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1062 self.assertTrue(hasattr(new_zoo, 'animals'))
1063 self.assertTrue(callable(new_zoo.animals))
1064 self.assertTrue(hasattr(new_zoo, 'global_'))
1065 self.assertTrue(callable(new_zoo.global_))
1066 self.assertTrue(hasattr(new_zoo, 'load'))
1067 self.assertTrue(callable(new_zoo.load))
1068 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1069 self.assertTrue(callable(new_zoo.loadNoTemplate))
1070 self.assertTrue(hasattr(new_zoo, 'my'))
1071 self.assertTrue(callable(new_zoo.my))
1072 self.assertTrue(hasattr(new_zoo, 'query'))
1073 self.assertTrue(callable(new_zoo.query))
1074 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1075 self.assertTrue(callable(new_zoo.scopedAnimals))
1076
Joe Gregorio003b6e42013-02-13 15:42:19 -05001077 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001078 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1079 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1080 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1081 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1082 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1083 # _http, _model and _schema won't be equal since we will get new
1084 # instances upon un-pickling
1085
1086 def _dummy_zoo_request(self):
1087 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1088 zoo_contents = fh.read()
1089
1090 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1091 {'api': 'zoo', 'apiVersion': 'v1'})
1092 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001093 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1094 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001095
1096 http = httplib2.Http()
1097 original_request = http.request
1098 def wrapped_request(uri, method='GET', *args, **kwargs):
1099 if uri == zoo_uri:
1100 return httplib2.Response({'status': '200'}), zoo_contents
1101 return original_request(uri, method=method, *args, **kwargs)
1102 http.request = wrapped_request
1103 return http
1104
1105 def _dummy_token(self):
1106 access_token = 'foo'
1107 client_id = 'some_client_id'
1108 client_secret = 'cOuDdkfjxxnv+'
1109 refresh_token = '1/0/a.df219fjls0'
1110 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001111 user_agent = 'refresh_checker/1.0'
1112 return OAuth2Credentials(
1113 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001114 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001115 user_agent)
1116
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001117 def test_pickle_with_credentials(self):
1118 credentials = self._dummy_token()
1119 http = self._dummy_zoo_request()
1120 http = credentials.authorize(http)
1121 self.assertTrue(hasattr(http.request, 'credentials'))
1122
1123 zoo = build('zoo', 'v1', http=http)
1124 pickled_zoo = pickle.dumps(zoo)
1125 new_zoo = pickle.loads(pickled_zoo)
1126 self.assertEqual(sorted(zoo.__dict__.keys()),
1127 sorted(new_zoo.__dict__.keys()))
1128 new_http = new_zoo._http
1129 self.assertFalse(hasattr(new_http.request, 'credentials'))
1130
Joe Gregorio708388c2012-06-15 13:43:04 -04001131
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001132class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001133
Joe Gregorio3c676f92011-07-25 10:38:14 -04001134 def test_next_successful_none_on_no_next_page_token(self):
1135 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001136 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001137 request = tasks.tasklists().list()
1138 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1139
1140 def test_next_successful_with_next_page_token(self):
1141 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001142 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001143 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001144 next_request = tasks.tasklists().list_next(
1145 request, {'nextPageToken': '123abc'})
Joe Gregorio3c676f92011-07-25 10:38:14 -04001146 parsed = list(urlparse.urlparse(next_request.uri))
1147 q = parse_qs(parsed[4])
1148 self.assertEqual(q['pageToken'][0], '123abc')
1149
Joe Gregorio555f33c2011-08-19 14:56:07 -04001150 def test_next_with_method_with_no_properties(self):
1151 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001152 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001153 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001154
Joe Gregorioa98733f2011-09-16 10:12:28 -04001155
Joe Gregorio708388c2012-06-15 13:43:04 -04001156class MediaGet(unittest.TestCase):
1157
1158 def test_get_media(self):
1159 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001160 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001161 request = zoo.animals().get_media(name='Lion')
1162
1163 parsed = urlparse.urlparse(request.uri)
1164 q = parse_qs(parsed[4])
1165 self.assertEqual(q['alt'], ['media'])
1166 self.assertEqual(request.headers['accept'], '*/*')
1167
1168 http = HttpMockSequence([
1169 ({'status': '200'}, 'standing in for media'),
1170 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001171 response = request.execute(http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001172 self.assertEqual('standing in for media', response)
1173
1174
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001175if __name__ == '__main__':
1176 unittest.main()