blob: 92213ac2374ab62072c79ddd3ac6aca41330e40c [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 Gregorio32f048f2012-08-27 16:31:27 -040028import gflags
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040029import httplib2
30import os
Joe Gregoriodc106fc2012-11-20 14:30:14 -050031import pickle
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040032import sys
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040033import unittest
Joe Gregorio00cf1d92010-09-27 09:22:03 -040034import urlparse
Joe Gregorio910b9b12012-06-12 09:36:30 -040035import StringIO
36
Joe Gregorioa98733f2011-09-16 10:12:28 -040037
ade@google.comc5eb46f2010-09-27 23:35:39 +010038try:
Daniel Hermesc2113242013-02-27 10:16:13 -080039 from urlparse import parse_qs
ade@google.comc5eb46f2010-09-27 23:35:39 +010040except ImportError:
Daniel Hermesc2113242013-02-27 10:16:13 -080041 from cgi import parse_qs
Joe Gregorio00cf1d92010-09-27 09:22:03 -040042
Joe Gregoriodc106fc2012-11-20 14:30:14 -050043
44from apiclient.discovery import _add_query_parameter
Daniel Hermesc2113242013-02-27 10:16:13 -080045from apiclient.discovery import _fix_up_media_upload
46from apiclient.discovery import _fix_up_method_description
47from apiclient.discovery import _fix_up_parameters
Joe Gregoriodc106fc2012-11-20 14:30:14 -050048from apiclient.discovery import build
49from apiclient.discovery import build_from_document
50from apiclient.discovery import DISCOVERY_URI
51from apiclient.discovery import key2param
Daniel Hermesc2113242013-02-27 10:16:13 -080052from apiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
Daniel Hermes954e1242013-02-28 09:28:37 -080053from apiclient.discovery import ResourceMethodParameters
Daniel Hermesc2113242013-02-27 10:16:13 -080054from apiclient.discovery import STACK_QUERY_PARAMETERS
55from apiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Joe Gregorioc0e0fe92011-03-04 16:16:55 -050056from apiclient.errors import HttpError
Joe Gregorio49396552011-03-08 10:39:00 -050057from apiclient.errors import InvalidJsonError
Joe Gregoriofdf7c802011-06-30 12:33:38 -040058from apiclient.errors import MediaUploadSizeError
Joe Gregoriod0bd3882011-11-22 09:49:47 -050059from apiclient.errors import ResumableUploadError
Joe Gregoriofdf7c802011-06-30 12:33:38 -040060from apiclient.errors import UnacceptableMimeTypeError
Joe Gregoriod0bd3882011-11-22 09:49:47 -050061from apiclient.http import HttpMock
62from apiclient.http import HttpMockSequence
63from apiclient.http import MediaFileUpload
Joe Gregorio910b9b12012-06-12 09:36:30 -040064from apiclient.http import MediaIoBaseUpload
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040065from apiclient.http import MediaUpload
Joe Gregoriod0bd3882011-11-22 09:49:47 -050066from apiclient.http import MediaUploadProgress
67from apiclient.http import tunnel_patch
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080068from oauth2client import GOOGLE_TOKEN_URI
Joe Gregorio910b9b12012-06-12 09:36:30 -040069from oauth2client.anyjson import simplejson
Joe Gregoriodc106fc2012-11-20 14:30:14 -050070from oauth2client.client import OAuth2Credentials
71import uritemplate
72
Joe Gregoriocb8103d2011-02-11 23:20:52 -050073
74DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
75
Joe Gregorio32f048f2012-08-27 16:31:27 -040076FLAGS = gflags.FLAGS
77FLAGS.positional_parameters_enforcement = 'EXCEPTION'
78
Joe Gregorioa98733f2011-09-16 10:12:28 -040079
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050080def assertUrisEqual(testcase, expected, actual):
81 """Test that URIs are the same, up to reordering of query parameters."""
82 expected = urlparse.urlparse(expected)
83 actual = urlparse.urlparse(actual)
84 testcase.assertEqual(expected.scheme, actual.scheme)
85 testcase.assertEqual(expected.netloc, actual.netloc)
86 testcase.assertEqual(expected.path, actual.path)
87 testcase.assertEqual(expected.params, actual.params)
88 testcase.assertEqual(expected.fragment, actual.fragment)
89 expected_query = parse_qs(expected.query)
90 actual_query = parse_qs(actual.query)
91 for name in expected_query.keys():
92 testcase.assertEqual(expected_query[name], actual_query[name])
93 for name in actual_query.keys():
94 testcase.assertEqual(expected_query[name], actual_query[name])
95
96
Joe Gregoriocb8103d2011-02-11 23:20:52 -050097def datafile(filename):
98 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040099
100
Joe Gregorio504a17f2012-12-07 14:14:26 -0500101class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800102
Joe Gregorio504a17f2012-12-07 14:14:26 -0500103 def test_retries(self):
104 # Merely loading apiclient.discovery should set the RETRIES to 1.
105 self.assertEqual(1, httplib2.RETRIES)
106
107
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400108class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800109
110 def setUp(self):
111 with open(datafile('zoo.json'), 'r') as fh:
112 self.zoo_root_desc = simplejson.loads(fh.read())
113 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800114 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
115 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Daniel Hermesc2113242013-02-27 10:16:13 -0800116
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400117 def test_key2param(self):
118 self.assertEqual('max_results', key2param('max-results'))
119 self.assertEqual('x007_bond', key2param('007-bond'))
120
Daniel Hermesc2113242013-02-27 10:16:13 -0800121 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
122 self.assertEqual(method_desc['httpMethod'], http_method)
123
124 method_desc_copy = copy.deepcopy(method_desc)
125 self.assertEqual(method_desc, method_desc_copy)
126
127 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
128
129 self.assertNotEqual(method_desc, method_desc_copy)
130
131 for param_name in STACK_QUERY_PARAMETERS:
132 self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
133 parameters[param_name])
134
135 for param_name, value in root_desc.get('parameters', {}).iteritems():
136 self.assertEqual(value, parameters[param_name])
137
138 return parameters
139
140 def test_fix_up_parameters_get(self):
141 parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
142 'GET', self.zoo_root_desc)
143 # Since http_method is 'GET'
144 self.assertFalse(parameters.has_key('body'))
145
146 def test_fix_up_parameters_insert(self):
147 parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
148 'POST', self.zoo_root_desc)
149 body = {
150 'description': 'The request body.',
151 'type': 'object',
152 'required': True,
153 '$ref': 'Animal',
154 }
155 self.assertEqual(parameters['body'], body)
156
157 def test_fix_up_parameters_check_body(self):
158 dummy_root_desc = {}
159 no_payload_http_method = 'DELETE'
160 with_payload_http_method = 'PUT'
161
162 invalid_method_desc = {'response': 'Who cares'}
163 valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
164
165 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
166 no_payload_http_method)
167 self.assertFalse(parameters.has_key('body'))
168
169 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
170 no_payload_http_method)
171 self.assertFalse(parameters.has_key('body'))
172
173 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
174 with_payload_http_method)
175 self.assertFalse(parameters.has_key('body'))
176
177 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
178 with_payload_http_method)
179 body = {
180 'description': 'The request body.',
181 'type': 'object',
182 'required': True,
183 'key1': 'value1',
184 'key2': 'value2',
185 }
186 self.assertEqual(parameters['body'], body)
187
188 def _base_fix_up_method_description_test(
189 self, method_desc, initial_parameters, final_parameters,
190 final_accept, final_max_size, final_media_path_url):
191 fake_root_desc = {'rootUrl': 'http://root/',
192 'servicePath': 'fake/'}
193 fake_path_url = 'fake-path/'
194
195 accept, max_size, media_path_url = _fix_up_media_upload(
196 method_desc, fake_root_desc, fake_path_url, initial_parameters)
197 self.assertEqual(accept, final_accept)
198 self.assertEqual(max_size, final_max_size)
199 self.assertEqual(media_path_url, final_media_path_url)
200 self.assertEqual(initial_parameters, final_parameters)
201
202 def test_fix_up_media_upload_no_initial_invalid(self):
203 invalid_method_desc = {'response': 'Who cares'}
204 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
205 [], 0, None)
206
207 def test_fix_up_media_upload_no_initial_valid_minimal(self):
208 valid_method_desc = {'mediaUpload': {'accept': []}}
209 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
210 self._base_fix_up_method_description_test(
211 valid_method_desc, {}, final_parameters, [], 0,
212 'http://root/upload/fake/fake-path/')
213
214 def test_fix_up_media_upload_no_initial_valid_full(self):
215 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
216 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
217 ten_gb = 10 * 2**30
218 self._base_fix_up_method_description_test(
219 valid_method_desc, {}, final_parameters, ['*/*'],
220 ten_gb, 'http://root/upload/fake/fake-path/')
221
222 def test_fix_up_media_upload_with_initial_invalid(self):
223 invalid_method_desc = {'response': 'Who cares'}
224 initial_parameters = {'body': {}}
225 self._base_fix_up_method_description_test(
226 invalid_method_desc, initial_parameters,
227 initial_parameters, [], 0, None)
228
229 def test_fix_up_media_upload_with_initial_valid_minimal(self):
230 valid_method_desc = {'mediaUpload': {'accept': []}}
231 initial_parameters = {'body': {}}
232 final_parameters = {'body': {'required': False},
233 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
234 self._base_fix_up_method_description_test(
235 valid_method_desc, initial_parameters, final_parameters, [], 0,
236 'http://root/upload/fake/fake-path/')
237
238 def test_fix_up_media_upload_with_initial_valid_full(self):
239 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
240 initial_parameters = {'body': {}}
241 final_parameters = {'body': {'required': False},
242 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
243 ten_gb = 10 * 2**30
244 self._base_fix_up_method_description_test(
245 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
246 ten_gb, 'http://root/upload/fake/fake-path/')
247
248 def test_fix_up_method_description_get(self):
249 result = _fix_up_method_description(self.zoo_get_method_desc,
250 self.zoo_root_desc)
251 path_url = 'query'
252 http_method = 'GET'
253 method_id = 'bigquery.query'
254 accept = []
255 max_size = 0L
256 media_path_url = None
257 self.assertEqual(result, (path_url, http_method, method_id, accept,
258 max_size, media_path_url))
259
260 def test_fix_up_method_description_insert(self):
261 result = _fix_up_method_description(self.zoo_insert_method_desc,
262 self.zoo_root_desc)
263 path_url = 'animals'
264 http_method = 'POST'
265 method_id = 'zoo.animals.insert'
266 accept = ['image/png']
267 max_size = 1024L
268 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
269 self.assertEqual(result, (path_url, http_method, method_id, accept,
270 max_size, media_path_url))
271
Daniel Hermes954e1242013-02-28 09:28:37 -0800272 def test_ResourceMethodParameters_zoo_get(self):
273 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
274
275 param_types = {'a': 'any',
276 'b': 'boolean',
277 'e': 'string',
278 'er': 'string',
279 'i': 'integer',
280 'n': 'number',
281 'o': 'object',
282 'q': 'string',
283 'rr': 'string'}
284 keys = param_types.keys()
285 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
286 self.assertEqual(parameters.required_params, [])
287 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
288 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
289 self.assertEqual(sorted(parameters.query_params),
290 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
291 self.assertEqual(parameters.path_params, set())
292 self.assertEqual(parameters.param_types, param_types)
293 enum_params = {'e': ['foo', 'bar'],
294 'er': ['one', 'two', 'three']}
295 self.assertEqual(parameters.enum_params, enum_params)
296
297 def test_ResourceMethodParameters_zoo_animals_patch(self):
298 method_desc = self.zoo_animals_resource['methods']['patch']
299 parameters = ResourceMethodParameters(method_desc)
300
301 param_types = {'name': 'string'}
302 keys = param_types.keys()
303 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
304 self.assertEqual(parameters.required_params, ['name'])
305 self.assertEqual(parameters.repeated_params, [])
306 self.assertEqual(parameters.pattern_params, {})
307 self.assertEqual(parameters.query_params, [])
308 self.assertEqual(parameters.path_params, set(['name']))
309 self.assertEqual(parameters.param_types, param_types)
310 self.assertEqual(parameters.enum_params, {})
311
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400312
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500313class DiscoveryErrors(unittest.TestCase):
314
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400315 def test_tests_should_be_run_with_strict_positional_enforcement(self):
316 try:
317 plus = build('plus', 'v1', None)
318 self.fail("should have raised a TypeError exception over missing http=.")
319 except TypeError:
320 pass
321
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500322 def test_failed_to_parse_discovery_json(self):
323 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
324 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400325 plus = build('plus', 'v1', http=self.http)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500326 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500327 except InvalidJsonError:
328 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500329
330
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100331class DiscoveryFromDocument(unittest.TestCase):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400332
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100333 def test_can_build_from_local_document(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500334 discovery = file(datafile('plus.json')).read()
335 plus = build_from_document(discovery, base="https://www.googleapis.com/")
336 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500337 self.assertTrue(hasattr(plus, 'activities'))
338
339 def test_can_build_from_local_deserialized_document(self):
340 discovery = file(datafile('plus.json')).read()
341 discovery = simplejson.loads(discovery)
342 plus = build_from_document(discovery, base="https://www.googleapis.com/")
343 self.assertTrue(plus is not None)
344 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400345
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100346 def test_building_with_base_remembers_base(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500347 discovery = file(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400348
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100349 base = "https://www.example.com/"
Joe Gregorio7b70f432011-11-09 10:18:51 -0500350 plus = build_from_document(discovery, base=base)
Joe Gregorioa2838152012-07-16 11:52:17 -0400351 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100352
353
Joe Gregorioa98733f2011-09-16 10:12:28 -0400354class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400355 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700356 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400357
Joe Gregorio583d9e42011-09-16 15:54:15 -0400358 def tearDown(self):
359 os.environ = self.old_environ
360
361 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400362 # build() will raise an HttpError on a 400, use this to pick the request uri
363 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400364 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400365 try:
366 http = HttpMockSequence([
367 ({'status': '400'}, file(datafile('zoo.json'), 'r').read()),
368 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400369 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400370 discoveryServiceUrl='http://example.com')
371 self.fail('Should have raised an exception.')
372 except HttpError, e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400373 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400374
Joe Gregorio583d9e42011-09-16 15:54:15 -0400375 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400376 # build() will raise an HttpError on a 400, use this to pick the request uri
377 # out of the raised exception.
378 try:
379 http = HttpMockSequence([
380 ({'status': '400'}, file(datafile('zoo.json'), 'r').read()),
381 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400382 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400383 discoveryServiceUrl='http://example.com')
384 self.fail('Should have raised an exception.')
385 except HttpError, e:
386 self.assertEqual(e.uri, 'http://example.com')
387
388
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400389class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400390
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400391 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500392 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400393 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400394
395 # Missing required parameters
396 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500397 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400398 self.fail()
399 except TypeError, e:
400 self.assertTrue('Missing' in str(e))
401
Joe Gregorio2467afa2012-06-20 12:21:25 -0400402 # Missing required parameters even if supplied as None.
403 try:
404 plus.activities().list(collection=None, userId=None)
405 self.fail()
406 except TypeError, e:
407 self.assertTrue('Missing' in str(e))
408
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400409 # Parameter doesn't match regex
410 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500411 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400412 self.fail()
413 except TypeError, e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500414 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400415
416 # Unexpected parameter
417 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500418 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400419 self.fail()
420 except TypeError, e:
421 self.assertTrue('unexpected' in str(e))
422
Joe Gregoriobee86832011-02-22 10:00:19 -0500423 def _check_query_types(self, request):
424 parsed = urlparse.urlparse(request.uri)
425 q = parse_qs(parsed[4])
426 self.assertEqual(q['q'], ['foo'])
427 self.assertEqual(q['i'], ['1'])
428 self.assertEqual(q['n'], ['1.0'])
429 self.assertEqual(q['b'], ['false'])
430 self.assertEqual(q['a'], ['[1, 2, 3]'])
431 self.assertEqual(q['o'], ['{\'a\': 1}'])
432 self.assertEqual(q['e'], ['bar'])
433
434 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400435 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400436 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500437
Joe Gregorioa98733f2011-09-16 10:12:28 -0400438 request = zoo.query(
439 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 -0500440 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400441 request = zoo.query(
442 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500443 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500444
Joe Gregorioa98733f2011-09-16 10:12:28 -0400445 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800446 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 -0500447
448 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800449 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
450 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500451 self._check_query_types(request)
452
Craig Citro1e742822012-03-01 12:59:22 -0800453 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500454 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800455
Joe Gregorio13217952011-02-22 15:37:38 -0500456 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400457 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400458 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400459 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500460
Joe Gregorioca876e42011-02-22 19:39:42 -0500461 parsed = urlparse.urlparse(request.uri)
462 q = parse_qs(parsed[4])
463 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400464 self.assertEqual(q['fields'], ['description'])
465
Joe Gregorio2467afa2012-06-20 12:21:25 -0400466 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400467 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400468 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400469 request = zoo.query(trace=None, fields='description')
470
471 parsed = urlparse.urlparse(request.uri)
472 q = parse_qs(parsed[4])
473 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400474
Joe Gregorioe08a1662011-12-07 09:48:22 -0500475 def test_model_added_query_parameters(self):
476 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400477 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500478 request = zoo.animals().get(name='Lion')
479
480 parsed = urlparse.urlparse(request.uri)
481 q = parse_qs(parsed[4])
482 self.assertEqual(q['alt'], ['json'])
483 self.assertEqual(request.headers['accept'], 'application/json')
484
485 def test_fallback_to_raw_model(self):
486 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400487 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500488 request = zoo.animals().getmedia(name='Lion')
489
490 parsed = urlparse.urlparse(request.uri)
491 q = parse_qs(parsed[4])
492 self.assertTrue('alt' not in q)
493 self.assertEqual(request.headers['accept'], '*/*')
494
Joe Gregoriof4153422011-03-18 22:45:18 -0400495 def test_patch(self):
496 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400497 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400498 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
499
500 self.assertEqual(request.method, 'PATCH')
501
502 def test_tunnel_patch(self):
503 http = HttpMockSequence([
504 ({'status': '200'}, file(datafile('zoo.json'), 'r').read()),
505 ({'status': '200'}, 'echo_request_headers_as_json'),
506 ])
507 http = tunnel_patch(http)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400508 zoo = build('zoo', 'v1', http=http)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400509 resp = zoo.animals().patch(
510 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400511
512 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500513
Joe Gregorio7b70f432011-11-09 10:18:51 -0500514 def test_plus_resources(self):
515 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400516 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500517 self.assertTrue(getattr(plus, 'activities'))
518 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400519
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400520 def test_full_featured(self):
521 # Zoo should exercise all discovery facets
522 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500523 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400524 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400525 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500526
Joe Gregoriof4153422011-03-18 22:45:18 -0400527 request = zoo.animals().list(name='bat', projection="full")
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400528 parsed = urlparse.urlparse(request.uri)
529 q = parse_qs(parsed[4])
530 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400531 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400532
Joe Gregorio3fada332011-01-07 17:07:45 -0500533 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500534 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400535 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500536 self.assertTrue(getattr(zoo, 'animals'))
537 request = zoo.my().favorites().list(max_results="5")
538 parsed = urlparse.urlparse(request.uri)
539 q = parse_qs(parsed[4])
540 self.assertEqual(q['max-results'], ['5'])
541
Joe Gregoriod92897c2011-07-07 11:44:56 -0400542 def test_methods_with_reserved_names(self):
543 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400544 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400545 self.assertTrue(getattr(zoo, 'animals'))
546 request = zoo.global_().print_().assert_(max_results="5")
547 parsed = urlparse.urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400548 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400549
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500550 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500551 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400552 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500553 self.assertTrue(getattr(zoo, 'query'))
554 request = zoo.query(q="foo")
555 parsed = urlparse.urlparse(request.uri)
556 q = parse_qs(parsed[4])
557 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400558
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400559 def test_simple_media_uploads(self):
560 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400561 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400562 doc = getattr(zoo.animals().insert, '__doc__')
563 self.assertTrue('media_body' in doc)
564
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400565 def test_simple_media_upload_no_max_size_provided(self):
566 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400567 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400568 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
569 self.assertEquals('image/png', request.headers['content-type'])
570 self.assertEquals('PNG', request.body[1:4])
571
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400572 def test_simple_media_raise_correct_exceptions(self):
573 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400574 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400575
576 try:
577 zoo.animals().insert(media_body=datafile('smiley.png'))
578 self.fail("should throw exception if media is too large.")
579 except MediaUploadSizeError:
580 pass
581
582 try:
583 zoo.animals().insert(media_body=datafile('small.jpg'))
584 self.fail("should throw exception if mimetype is unacceptable.")
585 except UnacceptableMimeTypeError:
586 pass
587
588 def test_simple_media_good_upload(self):
589 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400590 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400591
592 request = zoo.animals().insert(media_body=datafile('small.png'))
593 self.assertEquals('image/png', request.headers['content-type'])
594 self.assertEquals('PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500595 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400596 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500597 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400598
599 def test_multipart_media_raise_correct_exceptions(self):
600 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400601 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400602
603 try:
604 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
605 self.fail("should throw exception if media is too large.")
606 except MediaUploadSizeError:
607 pass
608
609 try:
610 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
611 self.fail("should throw exception if mimetype is unacceptable.")
612 except UnacceptableMimeTypeError:
613 pass
614
615 def test_multipart_media_good_upload(self):
616 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400617 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400618
619 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400620 self.assertTrue(request.headers['content-type'].startswith(
621 'multipart/related'))
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400622 self.assertEquals('--==', request.body[0:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500623 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400624 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500625 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400626
627 def test_media_capable_method_without_media(self):
628 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400629 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400630
631 request = zoo.animals().insert(body={})
632 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400633
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500634 def test_resumable_multipart_media_good_upload(self):
635 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400636 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500637
638 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
639 request = zoo.animals().insert(media_body=media_upload, body={})
640 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500641 'application/json'))
642 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500643 self.assertEquals(media_upload, request.resumable)
644
645 self.assertEquals('image/png', request.resumable.mimetype())
646
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500647 self.assertNotEquals(request.body, None)
648 self.assertEquals(request.resumable_uri, None)
649
650 http = HttpMockSequence([
651 ({'status': '200',
652 'location': 'http://upload.example.com'}, ''),
653 ({'status': '308',
654 'location': 'http://upload.example.com/2',
655 'range': '0-12'}, ''),
656 ({'status': '308',
657 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500658 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500659 ({'status': '200'}, '{"foo": "bar"}'),
660 ])
661
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400662 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500663 self.assertEquals(None, body)
664 self.assertTrue(isinstance(status, MediaUploadProgress))
665 self.assertEquals(13, status.resumable_progress)
666
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500667 # Two requests should have been made and the resumable_uri should have been
668 # updated for each one.
669 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
670
671 self.assertEquals(media_upload, request.resumable)
672 self.assertEquals(13, request.resumable_progress)
673
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400674 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500675 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500676 self.assertEquals(media_upload.size()-1, request.resumable_progress)
677 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500678
679 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400680 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500681 self.assertEquals(body, {"foo": "bar"})
682 self.assertEquals(status, None)
683
684
685 def test_resumable_media_good_upload(self):
686 """Not a multipart upload."""
687 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400688 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500689
690 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
691 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500692 self.assertEquals(media_upload, request.resumable)
693
694 self.assertEquals('image/png', request.resumable.mimetype())
695
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500696 self.assertEquals(request.body, None)
697 self.assertEquals(request.resumable_uri, None)
698
699 http = HttpMockSequence([
700 ({'status': '200',
701 'location': 'http://upload.example.com'}, ''),
702 ({'status': '308',
703 'location': 'http://upload.example.com/2',
704 'range': '0-12'}, ''),
705 ({'status': '308',
706 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500707 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500708 ({'status': '200'}, '{"foo": "bar"}'),
709 ])
710
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400711 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500712 self.assertEquals(None, body)
713 self.assertTrue(isinstance(status, MediaUploadProgress))
714 self.assertEquals(13, status.resumable_progress)
715
716 # Two requests should have been made and the resumable_uri should have been
717 # updated for each one.
718 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
719
720 self.assertEquals(media_upload, request.resumable)
721 self.assertEquals(13, request.resumable_progress)
722
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400723 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500724 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500725 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500726 self.assertEquals(request.body, None)
727
728 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400729 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500730 self.assertEquals(body, {"foo": "bar"})
731 self.assertEquals(status, None)
732
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500733 def test_resumable_media_good_upload_from_execute(self):
734 """Not a multipart upload."""
735 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400736 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500737
738 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
739 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500740 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400741 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500742 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500743
744 http = HttpMockSequence([
745 ({'status': '200',
746 'location': 'http://upload.example.com'}, ''),
747 ({'status': '308',
748 'location': 'http://upload.example.com/2',
749 'range': '0-12'}, ''),
750 ({'status': '308',
751 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500752 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500753 ({'status': '200'}, '{"foo": "bar"}'),
754 ])
755
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400756 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500757 self.assertEquals(body, {"foo": "bar"})
758
759 def test_resumable_media_fail_unknown_response_code_first_request(self):
760 """Not a multipart upload."""
761 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400762 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500763
764 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
765 request = zoo.animals().insert(media_body=media_upload, body=None)
766
767 http = HttpMockSequence([
768 ({'status': '400',
769 'location': 'http://upload.example.com'}, ''),
770 ])
771
Joe Gregoriobaf04802013-03-01 12:27:06 -0500772 try:
773 request.execute(http=http)
774 self.fail('Should have raised ResumableUploadError.')
775 except ResumableUploadError, e:
776 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500777
778 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
779 """Not a multipart upload."""
780 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400781 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500782
783 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
784 request = zoo.animals().insert(media_body=media_upload, body=None)
785
786 http = HttpMockSequence([
787 ({'status': '200',
788 'location': 'http://upload.example.com'}, ''),
789 ({'status': '400'}, ''),
790 ])
791
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400792 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400793 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500794
Joe Gregorio910b9b12012-06-12 09:36:30 -0400795 http = HttpMockSequence([
796 ({'status': '308',
797 'range': '0-5'}, ''),
798 ({'status': '308',
799 'range': '0-6'}, ''),
800 ])
801
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400802 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400803 self.assertEquals(status.resumable_progress, 7,
804 'Should have first checked length and then tried to PUT more.')
805 self.assertFalse(request._in_error_state)
806
807 # Put it back in an error state.
808 http = HttpMockSequence([
809 ({'status': '400'}, ''),
810 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400811 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400812 self.assertTrue(request._in_error_state)
813
814 # Pretend the last request that 400'd actually succeeded.
815 http = HttpMockSequence([
816 ({'status': '200'}, '{"foo": "bar"}'),
817 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400818 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400819 self.assertEqual(body, {'foo': 'bar'})
820
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400821 def test_media_io_base_stream_unlimited_chunksize_resume(self):
822 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
823 zoo = build('zoo', 'v1', http=self.http)
824
825 try:
826 import io
827
828 # Set up a seekable stream and try to upload in single chunk.
829 fd = io.BytesIO('01234"56789"')
830 media_upload = MediaIoBaseUpload(
831 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
832
833 request = zoo.animals().insert(media_body=media_upload, body=None)
834
835 # The single chunk fails, restart at the right point.
836 http = HttpMockSequence([
837 ({'status': '200',
838 'location': 'http://upload.example.com'}, ''),
839 ({'status': '308',
840 'location': 'http://upload.example.com/2',
841 'range': '0-4'}, ''),
842 ({'status': '200'}, 'echo_request_body'),
843 ])
844
845 body = request.execute(http=http)
846 self.assertEqual('56789', body)
847
848 except ImportError:
849 pass
850
Joe Gregorio5c120db2012-08-23 09:13:55 -0400851
852 def test_media_io_base_stream_chunksize_resume(self):
853 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
854 zoo = build('zoo', 'v1', http=self.http)
855
856 try:
857 import io
858
859 # Set up a seekable stream and try to upload in chunks.
860 fd = io.BytesIO('0123456789')
861 media_upload = MediaIoBaseUpload(
862 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
863
864 request = zoo.animals().insert(media_body=media_upload, body=None)
865
866 # The single chunk fails, pull the content sent out of the exception.
867 http = HttpMockSequence([
868 ({'status': '200',
869 'location': 'http://upload.example.com'}, ''),
870 ({'status': '400'}, 'echo_request_body'),
871 ])
872
873 try:
874 body = request.execute(http=http)
875 except HttpError, e:
876 self.assertEqual('01234', e.content)
877
878 except ImportError:
879 pass
880
881
Joe Gregorio910b9b12012-06-12 09:36:30 -0400882 def test_resumable_media_handle_uploads_of_unknown_size(self):
883 http = HttpMockSequence([
884 ({'status': '200',
885 'location': 'http://upload.example.com'}, ''),
886 ({'status': '200'}, 'echo_request_headers_as_json'),
887 ])
888
889 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400890 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400891
Joe Gregorio910b9b12012-06-12 09:36:30 -0400892 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400893 class IoBaseUnknownLength(MediaUpload):
894 def chunksize(self):
895 return 10
896
897 def mimetype(self):
898 return 'image/png'
899
900 def size(self):
901 return None
902
903 def resumable(self):
904 return True
905
906 def getbytes(self, begin, length):
907 return '0123456789'
908
909 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400910
911 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400912 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400913 self.assertEqual(body, {
914 'Content-Range': 'bytes 0-9/*',
915 'Content-Length': '10',
916 })
Joe Gregorio44454e42012-06-15 08:38:53 -0400917
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400918 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
919 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
920 zoo = build('zoo', 'v1', http=self.http)
921
922 class IoBaseHasStream(MediaUpload):
923 def chunksize(self):
924 return 10
925
926 def mimetype(self):
927 return 'image/png'
928
929 def size(self):
930 return None
931
932 def resumable(self):
933 return True
934
935 def getbytes(self, begin, length):
936 return '0123456789'
937
938 def has_stream(self):
939 return True
940
941 def stream(self):
942 raise NotImplementedError()
943
944 upload = IoBaseHasStream()
945
946 orig_version = sys.version_info
947 sys.version_info = (2, 5, 5, 'final', 0)
948
949 request = zoo.animals().insert(media_body=upload, body=None)
950
951 http = HttpMockSequence([
952 ({'status': '200',
953 'location': 'http://upload.example.com'}, ''),
954 ({'status': '200'}, 'echo_request_headers_as_json'),
955 ])
956
957 # This should not raise an exception because stream() shouldn't be called.
958 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400959 self.assertEqual(body, {
960 'Content-Range': 'bytes 0-9/*',
961 'Content-Length': '10'
962 })
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400963
964 sys.version_info = (2, 6, 5, 'final', 0)
965
966 request = zoo.animals().insert(media_body=upload, body=None)
967
968 # This should raise an exception because stream() will be called.
969 http = HttpMockSequence([
970 ({'status': '200',
971 'location': 'http://upload.example.com'}, ''),
972 ({'status': '200'}, 'echo_request_headers_as_json'),
973 ])
974
975 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
976
977 sys.version_info = orig_version
978
Joe Gregorio44454e42012-06-15 08:38:53 -0400979 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
980 http = HttpMockSequence([
981 ({'status': '200',
982 'location': 'http://upload.example.com'}, ''),
983 ({'status': '200'}, 'echo_request_headers_as_json'),
984 ])
985
986 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400987 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -0400988
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400989 fd = StringIO.StringIO('data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -0400990
991 # Create an upload that doesn't know the full size of the media.
992 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -0400993 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -0400994
995 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400996 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400997 self.assertEqual(body, {
998 'Content-Range': 'bytes 0-13/14',
999 'Content-Length': '14',
1000 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001001
1002 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1003 http = HttpMockSequence([
1004 ({'status': '200',
1005 'location': 'http://upload.example.com'}, ''),
1006 ({'status': '400'}, ''),
1007 ])
1008
1009 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001010 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001011
1012 # Create an upload that doesn't know the full size of the media.
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001013 fd = StringIO.StringIO('data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001014
1015 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001016 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001017
1018 request = zoo.animals().insert(media_body=upload, body=None)
1019
1020 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001021 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001022
1023 http = HttpMockSequence([
1024 ({'status': '400',
1025 'range': '0-5'}, 'echo_request_headers_as_json'),
1026 ])
1027 try:
1028 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001029 request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001030 except HttpError, e:
1031 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001032 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001033 'content-length': '0'
1034 }
1035 self.assertEqual(expected, simplejson.loads(e.content),
1036 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001037
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001038 def test_pickle(self):
1039 sorted_resource_keys = ['_baseUrl',
1040 '_developerKey',
1041 '_dynamic_attrs',
1042 '_http',
1043 '_model',
1044 '_requestBuilder',
1045 '_resourceDesc',
1046 '_rootDesc',
1047 '_schema',
1048 'animals',
1049 'global_',
1050 'load',
1051 'loadNoTemplate',
1052 'my',
1053 'query',
1054 'scopedAnimals']
1055
1056 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1057 zoo = build('zoo', 'v1', http=http)
1058 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1059
1060 pickled_zoo = pickle.dumps(zoo)
1061 new_zoo = pickle.loads(pickled_zoo)
1062 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1063 self.assertTrue(hasattr(new_zoo, 'animals'))
1064 self.assertTrue(callable(new_zoo.animals))
1065 self.assertTrue(hasattr(new_zoo, 'global_'))
1066 self.assertTrue(callable(new_zoo.global_))
1067 self.assertTrue(hasattr(new_zoo, 'load'))
1068 self.assertTrue(callable(new_zoo.load))
1069 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1070 self.assertTrue(callable(new_zoo.loadNoTemplate))
1071 self.assertTrue(hasattr(new_zoo, 'my'))
1072 self.assertTrue(callable(new_zoo.my))
1073 self.assertTrue(hasattr(new_zoo, 'query'))
1074 self.assertTrue(callable(new_zoo.query))
1075 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1076 self.assertTrue(callable(new_zoo.scopedAnimals))
1077
Joe Gregorio003b6e42013-02-13 15:42:19 -05001078 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001079 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1080 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1081 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1082 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1083 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1084 # _http, _model and _schema won't be equal since we will get new
1085 # instances upon un-pickling
1086
1087 def _dummy_zoo_request(self):
1088 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1089 zoo_contents = fh.read()
1090
1091 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1092 {'api': 'zoo', 'apiVersion': 'v1'})
1093 if 'REMOTE_ADDR' in os.environ:
1094 zoo_uri = _add_query_parameter(zoo_uri, 'userIp',
1095 os.environ['REMOTE_ADDR'])
1096
1097 http = httplib2.Http()
1098 original_request = http.request
1099 def wrapped_request(uri, method='GET', *args, **kwargs):
1100 if uri == zoo_uri:
1101 return httplib2.Response({'status': '200'}), zoo_contents
1102 return original_request(uri, method=method, *args, **kwargs)
1103 http.request = wrapped_request
1104 return http
1105
1106 def _dummy_token(self):
1107 access_token = 'foo'
1108 client_id = 'some_client_id'
1109 client_secret = 'cOuDdkfjxxnv+'
1110 refresh_token = '1/0/a.df219fjls0'
1111 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001112 user_agent = 'refresh_checker/1.0'
1113 return OAuth2Credentials(
1114 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001115 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001116 user_agent)
1117
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001118 def test_pickle_with_credentials(self):
1119 credentials = self._dummy_token()
1120 http = self._dummy_zoo_request()
1121 http = credentials.authorize(http)
1122 self.assertTrue(hasattr(http.request, 'credentials'))
1123
1124 zoo = build('zoo', 'v1', http=http)
1125 pickled_zoo = pickle.dumps(zoo)
1126 new_zoo = pickle.loads(pickled_zoo)
1127 self.assertEqual(sorted(zoo.__dict__.keys()),
1128 sorted(new_zoo.__dict__.keys()))
1129 new_http = new_zoo._http
1130 self.assertFalse(hasattr(new_http.request, 'credentials'))
1131
Joe Gregorio708388c2012-06-15 13:43:04 -04001132
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001133class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001134
Joe Gregorio3c676f92011-07-25 10:38:14 -04001135 def test_next_successful_none_on_no_next_page_token(self):
1136 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001137 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001138 request = tasks.tasklists().list()
1139 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1140
1141 def test_next_successful_with_next_page_token(self):
1142 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001143 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001144 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001145 next_request = tasks.tasklists().list_next(
1146 request, {'nextPageToken': '123abc'})
Joe Gregorio3c676f92011-07-25 10:38:14 -04001147 parsed = list(urlparse.urlparse(next_request.uri))
1148 q = parse_qs(parsed[4])
1149 self.assertEqual(q['pageToken'][0], '123abc')
1150
Joe Gregorio555f33c2011-08-19 14:56:07 -04001151 def test_next_with_method_with_no_properties(self):
1152 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001153 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001154 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001155
Joe Gregorioa98733f2011-09-16 10:12:28 -04001156
Joe Gregorio708388c2012-06-15 13:43:04 -04001157class MediaGet(unittest.TestCase):
1158
1159 def test_get_media(self):
1160 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001161 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001162 request = zoo.animals().get_media(name='Lion')
1163
1164 parsed = urlparse.urlparse(request.uri)
1165 q = parse_qs(parsed[4])
1166 self.assertEqual(q['alt'], ['media'])
1167 self.assertEqual(request.headers['accept'], '*/*')
1168
1169 http = HttpMockSequence([
1170 ({'status': '200'}, 'standing in for media'),
1171 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001172 response = request.execute(http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001173 self.assertEqual('standing in for media', response)
1174
1175
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001176if __name__ == '__main__':
1177 unittest.main()