blob: 032975c8d9789e0b0c659344aff02a925bffb4bc [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#
Craig Citro751b7fb2014-09-23 11:20:38 -07004# Copyright 2014 Google Inc. All Rights Reserved.
Joe Gregorio6d5e94f2010-08-25 23:49:30 -04005#
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
Craig Citro6ae34d72014-08-18 23:10:09 -070029import json
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040030import 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
John Asmuth864311d2014-04-24 15:46:08 -040044from googleapiclient.discovery import _fix_up_media_upload
45from googleapiclient.discovery import _fix_up_method_description
46from googleapiclient.discovery import _fix_up_parameters
47from googleapiclient.discovery import build
48from googleapiclient.discovery import build_from_document
49from googleapiclient.discovery import DISCOVERY_URI
50from googleapiclient.discovery import key2param
51from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
52from googleapiclient.discovery import ResourceMethodParameters
53from googleapiclient.discovery import STACK_QUERY_PARAMETERS
54from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
55from googleapiclient.errors import HttpError
56from googleapiclient.errors import InvalidJsonError
57from googleapiclient.errors import MediaUploadSizeError
58from googleapiclient.errors import ResumableUploadError
59from googleapiclient.errors import UnacceptableMimeTypeError
60from googleapiclient.http import HttpMock
61from googleapiclient.http import HttpMockSequence
62from googleapiclient.http import MediaFileUpload
63from googleapiclient.http import MediaIoBaseUpload
64from googleapiclient.http import MediaUpload
65from googleapiclient.http import MediaUploadProgress
66from googleapiclient.http import tunnel_patch
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080067from oauth2client import GOOGLE_TOKEN_URI
Joe Gregorio79daca02013-03-29 16:25:52 -040068from oauth2client import util
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:
Craig Citro6ae34d72014-08-18 23:10:09 -0700111 self.zoo_root_desc = json.loads(fh.read())
Daniel Hermesc2113242013-02-27 10:16:13 -0800112 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()
Craig Citro6ae34d72014-08-18 23:10:09 -0700340 discovery = json.loads(discovery)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500341 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.')
INADA Naokic1505df2014-08-20 15:19:53 +0900371 except HttpError as 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.')
INADA Naokic1505df2014-08-20 15:19:53 +0900384 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400385 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()
INADA Naokic1505df2014-08-20 15:19:53 +0900398 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400399 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()
INADA Naokic1505df2014-08-20 15:19:53 +0900405 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400406 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()
INADA Naokic1505df2014-08-20 15:19:53 +0900412 except TypeError as 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()
INADA Naokic1505df2014-08-20 15:19:53 +0900419 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400420 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
Orest Bolohane92c9002014-05-30 11:15:43 -0700519 def test_credentials(self):
520 class CredentialsMock:
521 def create_scoped_required(self):
522 return False
523
524 def authorize(self, http):
525 http.orest = True
526
527 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
528 build('plus', 'v1', http=self.http, credentials=None)
529 self.assertFalse(hasattr(self.http, 'orest'))
530 build('plus', 'v1', http=self.http, credentials=CredentialsMock())
531 self.assertTrue(hasattr(self.http, 'orest'))
532
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400533 def test_full_featured(self):
534 # Zoo should exercise all discovery facets
535 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500536 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400537 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400538 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500539
Joe Gregoriof4153422011-03-18 22:45:18 -0400540 request = zoo.animals().list(name='bat', projection="full")
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400541 parsed = urlparse.urlparse(request.uri)
542 q = parse_qs(parsed[4])
543 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400544 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400545
Joe Gregorio3fada332011-01-07 17:07:45 -0500546 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500547 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400548 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500549 self.assertTrue(getattr(zoo, 'animals'))
550 request = zoo.my().favorites().list(max_results="5")
551 parsed = urlparse.urlparse(request.uri)
552 q = parse_qs(parsed[4])
553 self.assertEqual(q['max-results'], ['5'])
554
Joe Gregoriod92897c2011-07-07 11:44:56 -0400555 def test_methods_with_reserved_names(self):
556 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400557 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400558 self.assertTrue(getattr(zoo, 'animals'))
559 request = zoo.global_().print_().assert_(max_results="5")
560 parsed = urlparse.urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400561 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400562
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500563 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500564 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400565 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500566 self.assertTrue(getattr(zoo, 'query'))
567 request = zoo.query(q="foo")
568 parsed = urlparse.urlparse(request.uri)
569 q = parse_qs(parsed[4])
570 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400571
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400572 def test_simple_media_uploads(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 doc = getattr(zoo.animals().insert, '__doc__')
576 self.assertTrue('media_body' in doc)
577
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400578 def test_simple_media_upload_no_max_size_provided(self):
579 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400580 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400581 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
582 self.assertEquals('image/png', request.headers['content-type'])
583 self.assertEquals('PNG', request.body[1:4])
584
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400585 def test_simple_media_raise_correct_exceptions(self):
586 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400587 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400588
589 try:
590 zoo.animals().insert(media_body=datafile('smiley.png'))
591 self.fail("should throw exception if media is too large.")
592 except MediaUploadSizeError:
593 pass
594
595 try:
596 zoo.animals().insert(media_body=datafile('small.jpg'))
597 self.fail("should throw exception if mimetype is unacceptable.")
598 except UnacceptableMimeTypeError:
599 pass
600
601 def test_simple_media_good_upload(self):
602 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400603 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400604
605 request = zoo.animals().insert(media_body=datafile('small.png'))
606 self.assertEquals('image/png', request.headers['content-type'])
607 self.assertEquals('PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500608 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400609 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500610 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400611
612 def test_multipart_media_raise_correct_exceptions(self):
613 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400614 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400615
616 try:
617 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
618 self.fail("should throw exception if media is too large.")
619 except MediaUploadSizeError:
620 pass
621
622 try:
623 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
624 self.fail("should throw exception if mimetype is unacceptable.")
625 except UnacceptableMimeTypeError:
626 pass
627
628 def test_multipart_media_good_upload(self):
629 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400630 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400631
632 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400633 self.assertTrue(request.headers['content-type'].startswith(
634 'multipart/related'))
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400635 self.assertEquals('--==', request.body[0:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500636 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400637 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500638 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400639
640 def test_media_capable_method_without_media(self):
641 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400642 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400643
644 request = zoo.animals().insert(body={})
645 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400646
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500647 def test_resumable_multipart_media_good_upload(self):
648 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400649 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500650
651 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
652 request = zoo.animals().insert(media_body=media_upload, body={})
653 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500654 'application/json'))
655 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500656 self.assertEquals(media_upload, request.resumable)
657
658 self.assertEquals('image/png', request.resumable.mimetype())
659
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500660 self.assertNotEquals(request.body, None)
661 self.assertEquals(request.resumable_uri, None)
662
663 http = HttpMockSequence([
664 ({'status': '200',
665 'location': 'http://upload.example.com'}, ''),
666 ({'status': '308',
667 'location': 'http://upload.example.com/2',
668 'range': '0-12'}, ''),
669 ({'status': '308',
670 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500671 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500672 ({'status': '200'}, '{"foo": "bar"}'),
673 ])
674
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400675 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500676 self.assertEquals(None, body)
677 self.assertTrue(isinstance(status, MediaUploadProgress))
678 self.assertEquals(13, status.resumable_progress)
679
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500680 # Two requests should have been made and the resumable_uri should have been
681 # updated for each one.
682 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
683
684 self.assertEquals(media_upload, request.resumable)
685 self.assertEquals(13, request.resumable_progress)
686
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400687 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500688 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500689 self.assertEquals(media_upload.size()-1, request.resumable_progress)
690 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500691
692 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400693 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500694 self.assertEquals(body, {"foo": "bar"})
695 self.assertEquals(status, None)
696
697
698 def test_resumable_media_good_upload(self):
699 """Not a multipart upload."""
700 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400701 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500702
703 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
704 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500705 self.assertEquals(media_upload, request.resumable)
706
707 self.assertEquals('image/png', request.resumable.mimetype())
708
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500709 self.assertEquals(request.body, None)
710 self.assertEquals(request.resumable_uri, None)
711
712 http = HttpMockSequence([
713 ({'status': '200',
714 'location': 'http://upload.example.com'}, ''),
715 ({'status': '308',
716 'location': 'http://upload.example.com/2',
717 'range': '0-12'}, ''),
718 ({'status': '308',
719 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500720 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500721 ({'status': '200'}, '{"foo": "bar"}'),
722 ])
723
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400724 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500725 self.assertEquals(None, body)
726 self.assertTrue(isinstance(status, MediaUploadProgress))
727 self.assertEquals(13, status.resumable_progress)
728
729 # Two requests should have been made and the resumable_uri should have been
730 # updated for each one.
731 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
732
733 self.assertEquals(media_upload, request.resumable)
734 self.assertEquals(13, request.resumable_progress)
735
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400736 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500737 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500738 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500739 self.assertEquals(request.body, None)
740
741 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400742 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500743 self.assertEquals(body, {"foo": "bar"})
744 self.assertEquals(status, None)
745
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500746 def test_resumable_media_good_upload_from_execute(self):
747 """Not a multipart upload."""
748 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400749 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500750
751 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
752 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500753 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400754 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500755 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500756
757 http = HttpMockSequence([
758 ({'status': '200',
759 'location': 'http://upload.example.com'}, ''),
760 ({'status': '308',
761 'location': 'http://upload.example.com/2',
762 'range': '0-12'}, ''),
763 ({'status': '308',
764 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500765 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500766 ({'status': '200'}, '{"foo": "bar"}'),
767 ])
768
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400769 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500770 self.assertEquals(body, {"foo": "bar"})
771
772 def test_resumable_media_fail_unknown_response_code_first_request(self):
773 """Not a multipart upload."""
774 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400775 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500776
777 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
778 request = zoo.animals().insert(media_body=media_upload, body=None)
779
780 http = HttpMockSequence([
781 ({'status': '400',
782 'location': 'http://upload.example.com'}, ''),
783 ])
784
Joe Gregoriobaf04802013-03-01 12:27:06 -0500785 try:
786 request.execute(http=http)
787 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +0900788 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -0500789 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500790
791 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
792 """Not a multipart upload."""
793 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400794 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500795
796 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
797 request = zoo.animals().insert(media_body=media_upload, body=None)
798
799 http = HttpMockSequence([
800 ({'status': '200',
801 'location': 'http://upload.example.com'}, ''),
802 ({'status': '400'}, ''),
803 ])
804
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400805 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400806 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500807
Joe Gregorio910b9b12012-06-12 09:36:30 -0400808 http = HttpMockSequence([
809 ({'status': '308',
810 'range': '0-5'}, ''),
811 ({'status': '308',
812 'range': '0-6'}, ''),
813 ])
814
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400815 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400816 self.assertEquals(status.resumable_progress, 7,
817 'Should have first checked length and then tried to PUT more.')
818 self.assertFalse(request._in_error_state)
819
820 # Put it back in an error state.
821 http = HttpMockSequence([
822 ({'status': '400'}, ''),
823 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400824 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400825 self.assertTrue(request._in_error_state)
826
827 # Pretend the last request that 400'd actually succeeded.
828 http = HttpMockSequence([
829 ({'status': '200'}, '{"foo": "bar"}'),
830 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400831 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400832 self.assertEqual(body, {'foo': 'bar'})
833
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400834 def test_media_io_base_stream_unlimited_chunksize_resume(self):
835 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
836 zoo = build('zoo', 'v1', http=self.http)
837
838 try:
839 import io
840
841 # Set up a seekable stream and try to upload in single chunk.
842 fd = io.BytesIO('01234"56789"')
843 media_upload = MediaIoBaseUpload(
844 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
845
846 request = zoo.animals().insert(media_body=media_upload, body=None)
847
848 # The single chunk fails, restart at the right point.
849 http = HttpMockSequence([
850 ({'status': '200',
851 'location': 'http://upload.example.com'}, ''),
852 ({'status': '308',
853 'location': 'http://upload.example.com/2',
854 'range': '0-4'}, ''),
855 ({'status': '200'}, 'echo_request_body'),
856 ])
857
858 body = request.execute(http=http)
859 self.assertEqual('56789', body)
860
861 except ImportError:
862 pass
863
Joe Gregorio5c120db2012-08-23 09:13:55 -0400864
865 def test_media_io_base_stream_chunksize_resume(self):
866 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
867 zoo = build('zoo', 'v1', http=self.http)
868
869 try:
870 import io
871
872 # Set up a seekable stream and try to upload in chunks.
873 fd = io.BytesIO('0123456789')
874 media_upload = MediaIoBaseUpload(
875 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
876
877 request = zoo.animals().insert(media_body=media_upload, body=None)
878
879 # The single chunk fails, pull the content sent out of the exception.
880 http = HttpMockSequence([
881 ({'status': '200',
882 'location': 'http://upload.example.com'}, ''),
883 ({'status': '400'}, 'echo_request_body'),
884 ])
885
886 try:
887 body = request.execute(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +0900888 except HttpError as e:
Joe Gregorio5c120db2012-08-23 09:13:55 -0400889 self.assertEqual('01234', e.content)
890
891 except ImportError:
892 pass
893
894
Joe Gregorio910b9b12012-06-12 09:36:30 -0400895 def test_resumable_media_handle_uploads_of_unknown_size(self):
896 http = HttpMockSequence([
897 ({'status': '200',
898 'location': 'http://upload.example.com'}, ''),
899 ({'status': '200'}, 'echo_request_headers_as_json'),
900 ])
901
902 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400903 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400904
Joe Gregorio910b9b12012-06-12 09:36:30 -0400905 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400906 class IoBaseUnknownLength(MediaUpload):
907 def chunksize(self):
908 return 10
909
910 def mimetype(self):
911 return 'image/png'
912
913 def size(self):
914 return None
915
916 def resumable(self):
917 return True
918
919 def getbytes(self, begin, length):
920 return '0123456789'
921
922 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400923
924 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400925 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400926 self.assertEqual(body, {
927 'Content-Range': 'bytes 0-9/*',
928 'Content-Length': '10',
929 })
Joe Gregorio44454e42012-06-15 08:38:53 -0400930
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400931 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
932 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
933 zoo = build('zoo', 'v1', http=self.http)
934
935 class IoBaseHasStream(MediaUpload):
936 def chunksize(self):
937 return 10
938
939 def mimetype(self):
940 return 'image/png'
941
942 def size(self):
943 return None
944
945 def resumable(self):
946 return True
947
948 def getbytes(self, begin, length):
949 return '0123456789'
950
951 def has_stream(self):
952 return True
953
954 def stream(self):
955 raise NotImplementedError()
956
957 upload = IoBaseHasStream()
958
959 orig_version = sys.version_info
960 sys.version_info = (2, 5, 5, 'final', 0)
961
962 request = zoo.animals().insert(media_body=upload, body=None)
963
964 http = HttpMockSequence([
965 ({'status': '200',
966 'location': 'http://upload.example.com'}, ''),
967 ({'status': '200'}, 'echo_request_headers_as_json'),
968 ])
969
970 # This should not raise an exception because stream() shouldn't be called.
971 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400972 self.assertEqual(body, {
973 'Content-Range': 'bytes 0-9/*',
974 'Content-Length': '10'
975 })
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400976
977 sys.version_info = (2, 6, 5, 'final', 0)
978
979 request = zoo.animals().insert(media_body=upload, body=None)
980
981 # This should raise an exception because stream() will be called.
982 http = HttpMockSequence([
983 ({'status': '200',
984 'location': 'http://upload.example.com'}, ''),
985 ({'status': '200'}, 'echo_request_headers_as_json'),
986 ])
987
988 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
989
990 sys.version_info = orig_version
991
Joe Gregorio44454e42012-06-15 08:38:53 -0400992 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
993 http = HttpMockSequence([
994 ({'status': '200',
995 'location': 'http://upload.example.com'}, ''),
996 ({'status': '200'}, 'echo_request_headers_as_json'),
997 ])
998
999 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001000 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001001
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001002 fd = StringIO.StringIO('data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001003
1004 # Create an upload that doesn't know the full size of the media.
1005 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001006 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001007
1008 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001009 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001010 self.assertEqual(body, {
1011 'Content-Range': 'bytes 0-13/14',
1012 'Content-Length': '14',
1013 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001014
1015 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1016 http = HttpMockSequence([
1017 ({'status': '200',
1018 'location': 'http://upload.example.com'}, ''),
1019 ({'status': '400'}, ''),
1020 ])
1021
1022 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001023 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001024
1025 # Create an upload that doesn't know the full size of the media.
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001026 fd = StringIO.StringIO('data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001027
1028 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001029 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001030
1031 request = zoo.animals().insert(media_body=upload, body=None)
1032
1033 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001034 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001035
1036 http = HttpMockSequence([
1037 ({'status': '400',
1038 'range': '0-5'}, 'echo_request_headers_as_json'),
1039 ])
1040 try:
1041 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001042 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001043 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001044 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001045 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001046 'content-length': '0'
1047 }
Craig Citro6ae34d72014-08-18 23:10:09 -07001048 self.assertEqual(expected, json.loads(e.content),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001049 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001050
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001051 def test_pickle(self):
1052 sorted_resource_keys = ['_baseUrl',
1053 '_developerKey',
1054 '_dynamic_attrs',
1055 '_http',
1056 '_model',
1057 '_requestBuilder',
1058 '_resourceDesc',
1059 '_rootDesc',
1060 '_schema',
1061 'animals',
1062 'global_',
1063 'load',
1064 'loadNoTemplate',
1065 'my',
1066 'query',
1067 'scopedAnimals']
1068
1069 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1070 zoo = build('zoo', 'v1', http=http)
1071 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1072
1073 pickled_zoo = pickle.dumps(zoo)
1074 new_zoo = pickle.loads(pickled_zoo)
1075 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1076 self.assertTrue(hasattr(new_zoo, 'animals'))
1077 self.assertTrue(callable(new_zoo.animals))
1078 self.assertTrue(hasattr(new_zoo, 'global_'))
1079 self.assertTrue(callable(new_zoo.global_))
1080 self.assertTrue(hasattr(new_zoo, 'load'))
1081 self.assertTrue(callable(new_zoo.load))
1082 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1083 self.assertTrue(callable(new_zoo.loadNoTemplate))
1084 self.assertTrue(hasattr(new_zoo, 'my'))
1085 self.assertTrue(callable(new_zoo.my))
1086 self.assertTrue(hasattr(new_zoo, 'query'))
1087 self.assertTrue(callable(new_zoo.query))
1088 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1089 self.assertTrue(callable(new_zoo.scopedAnimals))
1090
Joe Gregorio003b6e42013-02-13 15:42:19 -05001091 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001092 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1093 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1094 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1095 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1096 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1097 # _http, _model and _schema won't be equal since we will get new
1098 # instances upon un-pickling
1099
1100 def _dummy_zoo_request(self):
1101 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1102 zoo_contents = fh.read()
1103
1104 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1105 {'api': 'zoo', 'apiVersion': 'v1'})
1106 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001107 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1108 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001109
1110 http = httplib2.Http()
1111 original_request = http.request
1112 def wrapped_request(uri, method='GET', *args, **kwargs):
1113 if uri == zoo_uri:
1114 return httplib2.Response({'status': '200'}), zoo_contents
1115 return original_request(uri, method=method, *args, **kwargs)
1116 http.request = wrapped_request
1117 return http
1118
1119 def _dummy_token(self):
1120 access_token = 'foo'
1121 client_id = 'some_client_id'
1122 client_secret = 'cOuDdkfjxxnv+'
1123 refresh_token = '1/0/a.df219fjls0'
1124 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001125 user_agent = 'refresh_checker/1.0'
1126 return OAuth2Credentials(
1127 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001128 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001129 user_agent)
1130
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001131 def test_pickle_with_credentials(self):
1132 credentials = self._dummy_token()
1133 http = self._dummy_zoo_request()
1134 http = credentials.authorize(http)
1135 self.assertTrue(hasattr(http.request, 'credentials'))
1136
1137 zoo = build('zoo', 'v1', http=http)
1138 pickled_zoo = pickle.dumps(zoo)
1139 new_zoo = pickle.loads(pickled_zoo)
1140 self.assertEqual(sorted(zoo.__dict__.keys()),
1141 sorted(new_zoo.__dict__.keys()))
1142 new_http = new_zoo._http
1143 self.assertFalse(hasattr(new_http.request, 'credentials'))
1144
Joe Gregorio708388c2012-06-15 13:43:04 -04001145
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001146class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001147
Joe Gregorio3c676f92011-07-25 10:38:14 -04001148 def test_next_successful_none_on_no_next_page_token(self):
1149 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001150 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001151 request = tasks.tasklists().list()
1152 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1153
1154 def test_next_successful_with_next_page_token(self):
1155 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001156 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001157 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001158 next_request = tasks.tasklists().list_next(
1159 request, {'nextPageToken': '123abc'})
Joe Gregorio3c676f92011-07-25 10:38:14 -04001160 parsed = list(urlparse.urlparse(next_request.uri))
1161 q = parse_qs(parsed[4])
1162 self.assertEqual(q['pageToken'][0], '123abc')
1163
Joe Gregorio555f33c2011-08-19 14:56:07 -04001164 def test_next_with_method_with_no_properties(self):
1165 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001166 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001167 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001168
Joe Gregorioa98733f2011-09-16 10:12:28 -04001169
Joe Gregorio708388c2012-06-15 13:43:04 -04001170class MediaGet(unittest.TestCase):
1171
1172 def test_get_media(self):
1173 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001174 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001175 request = zoo.animals().get_media(name='Lion')
1176
1177 parsed = urlparse.urlparse(request.uri)
1178 q = parse_qs(parsed[4])
1179 self.assertEqual(q['alt'], ['media'])
1180 self.assertEqual(request.headers['accept'], '*/*')
1181
1182 http = HttpMockSequence([
1183 ({'status': '200'}, 'standing in for media'),
1184 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001185 response = request.execute(http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001186 self.assertEqual('standing in for media', response)
1187
1188
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001189if __name__ == '__main__':
1190 unittest.main()