blob: 3d30d8ccb37a25e9242c159d7d3c8f1de091203e [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
John Asmuth864311d2014-04-24 15:46:08 -040043from googleapiclient.discovery import _fix_up_media_upload
44from googleapiclient.discovery import _fix_up_method_description
45from googleapiclient.discovery import _fix_up_parameters
46from googleapiclient.discovery import build
47from googleapiclient.discovery import build_from_document
48from googleapiclient.discovery import DISCOVERY_URI
49from googleapiclient.discovery import key2param
50from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
51from googleapiclient.discovery import ResourceMethodParameters
52from googleapiclient.discovery import STACK_QUERY_PARAMETERS
53from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
54from googleapiclient.errors import HttpError
55from googleapiclient.errors import InvalidJsonError
56from googleapiclient.errors import MediaUploadSizeError
57from googleapiclient.errors import ResumableUploadError
58from googleapiclient.errors import UnacceptableMimeTypeError
59from googleapiclient.http import HttpMock
60from googleapiclient.http import HttpMockSequence
61from googleapiclient.http import MediaFileUpload
62from googleapiclient.http import MediaIoBaseUpload
63from googleapiclient.http import MediaUpload
64from googleapiclient.http import MediaUploadProgress
65from googleapiclient.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):
John Asmuth864311d2014-04-24 15:46:08 -0400103 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
Joe Gregorio504a17f2012-12-07 14:14:26 -0500104 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()