blob: ec66bc6d396886fbd9792f7be55bff8f34a92b3a [file] [log] [blame]
Craig Citro15744b12015-03-02 13:34:32 -08001#!/usr/bin/env python
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"""
INADA Naokid898a372015-03-04 03:52:46 +090023from __future__ import absolute_import
24import six
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040025
26__author__ = 'jcgregorio@google.com (Joe Gregorio)'
27
Pat Ferateed9affd2015-03-03 16:03:15 -080028from six import BytesIO, StringIO
Pat Ferated5b61bd2015-03-03 16:04:11 -080029from six.moves.urllib.parse import urlparse, parse_qs
Pat Ferateed9affd2015-03-03 16:03:15 -080030
Daniel Hermesc2113242013-02-27 10:16:13 -080031import copy
Joe Gregoriodc106fc2012-11-20 14:30:14 -050032import datetime
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040033import httplib2
Craig Citro7ee535d2015-02-23 10:11:14 -080034import itertools
Craig Citro6ae34d72014-08-18 23:10:09 -070035import json
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040036import os
Joe Gregoriodc106fc2012-11-20 14:30:14 -050037import pickle
Phil Ruffwind26178fc2015-10-13 19:00:33 -040038import re
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040039import sys
Pat Ferate497a90f2015-03-09 09:52:54 -070040import unittest2 as unittest
Joe Gregoriodc106fc2012-11-20 14:30:14 -050041
Takashi Matsuo30125122015-08-19 11:42:32 -070042import mock
43
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080044import google.auth.credentials
45import google_auth_httplib2
John Asmuth864311d2014-04-24 15:46:08 -040046from googleapiclient.discovery import _fix_up_media_upload
47from googleapiclient.discovery import _fix_up_method_description
48from googleapiclient.discovery import _fix_up_parameters
Craig Citro7ee535d2015-02-23 10:11:14 -080049from googleapiclient.discovery import _urljoin
John Asmuth864311d2014-04-24 15:46:08 -040050from googleapiclient.discovery import build
51from googleapiclient.discovery import build_from_document
52from googleapiclient.discovery import DISCOVERY_URI
53from googleapiclient.discovery import key2param
54from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
Brian J. Watson38051ac2016-10-25 07:53:08 -070055from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE
John Asmuth864311d2014-04-24 15:46:08 -040056from googleapiclient.discovery import ResourceMethodParameters
57from googleapiclient.discovery import STACK_QUERY_PARAMETERS
58from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Takashi Matsuo30125122015-08-19 11:42:32 -070059from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE
60from googleapiclient.discovery_cache.base import Cache
John Asmuth864311d2014-04-24 15:46:08 -040061from googleapiclient.errors import HttpError
62from googleapiclient.errors import InvalidJsonError
63from googleapiclient.errors import MediaUploadSizeError
64from googleapiclient.errors import ResumableUploadError
65from googleapiclient.errors import UnacceptableMimeTypeError
Takashi Matsuo3772f9d2015-09-04 12:25:55 -070066from googleapiclient.errors import UnknownApiNameOrVersion
Brian J. Watson38051ac2016-10-25 07:53:08 -070067from googleapiclient.errors import UnknownFileType
Igor Maravić22435292017-01-19 22:28:22 +010068from googleapiclient.http import build_http
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -040069from googleapiclient.http import BatchHttpRequest
John Asmuth864311d2014-04-24 15:46:08 -040070from googleapiclient.http import HttpMock
71from googleapiclient.http import HttpMockSequence
72from googleapiclient.http import MediaFileUpload
73from googleapiclient.http import MediaIoBaseUpload
74from googleapiclient.http import MediaUpload
75from googleapiclient.http import MediaUploadProgress
76from googleapiclient.http import tunnel_patch
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080077from oauth2client import GOOGLE_TOKEN_URI
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080078from oauth2client.client import OAuth2Credentials, GoogleCredentials
Joe Gregorio79daca02013-03-29 16:25:52 -040079
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -070080try:
81 from oauth2client import util
82except ImportError:
83 from oauth2client import _helpers as util
84
Joe Gregoriodc106fc2012-11-20 14:30:14 -050085import uritemplate
86
Joe Gregoriocb8103d2011-02-11 23:20:52 -050087
88DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
89
Joe Gregorioa98733f2011-09-16 10:12:28 -040090
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050091def assertUrisEqual(testcase, expected, actual):
92 """Test that URIs are the same, up to reordering of query parameters."""
Pat Ferated5b61bd2015-03-03 16:04:11 -080093 expected = urlparse(expected)
94 actual = urlparse(actual)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050095 testcase.assertEqual(expected.scheme, actual.scheme)
96 testcase.assertEqual(expected.netloc, actual.netloc)
97 testcase.assertEqual(expected.path, actual.path)
98 testcase.assertEqual(expected.params, actual.params)
99 testcase.assertEqual(expected.fragment, actual.fragment)
100 expected_query = parse_qs(expected.query)
101 actual_query = parse_qs(actual.query)
INADA Naokid898a372015-03-04 03:52:46 +0900102 for name in list(expected_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500103 testcase.assertEqual(expected_query[name], actual_query[name])
INADA Naokid898a372015-03-04 03:52:46 +0900104 for name in list(actual_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500105 testcase.assertEqual(expected_query[name], actual_query[name])
106
107
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500108def datafile(filename):
109 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400110
111
Joe Gregorio504a17f2012-12-07 14:14:26 -0500112class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800113
Joe Gregorio504a17f2012-12-07 14:14:26 -0500114 def test_retries(self):
John Asmuth864311d2014-04-24 15:46:08 -0400115 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
Joe Gregorio504a17f2012-12-07 14:14:26 -0500116 self.assertEqual(1, httplib2.RETRIES)
117
118
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400119class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800120
121 def setUp(self):
122 with open(datafile('zoo.json'), 'r') as fh:
Craig Citro6ae34d72014-08-18 23:10:09 -0700123 self.zoo_root_desc = json.loads(fh.read())
Daniel Hermesc2113242013-02-27 10:16:13 -0800124 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800125 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
126 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Daniel Hermesc2113242013-02-27 10:16:13 -0800127
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400128 def test_key2param(self):
129 self.assertEqual('max_results', key2param('max-results'))
130 self.assertEqual('x007_bond', key2param('007-bond'))
131
Daniel Hermesc2113242013-02-27 10:16:13 -0800132 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
133 self.assertEqual(method_desc['httpMethod'], http_method)
134
135 method_desc_copy = copy.deepcopy(method_desc)
136 self.assertEqual(method_desc, method_desc_copy)
137
138 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
139
140 self.assertNotEqual(method_desc, method_desc_copy)
141
142 for param_name in STACK_QUERY_PARAMETERS:
143 self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
144 parameters[param_name])
145
INADA Naokid898a372015-03-04 03:52:46 +0900146 for param_name, value in six.iteritems(root_desc.get('parameters', {})):
Daniel Hermesc2113242013-02-27 10:16:13 -0800147 self.assertEqual(value, parameters[param_name])
148
149 return parameters
150
151 def test_fix_up_parameters_get(self):
152 parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
153 'GET', self.zoo_root_desc)
154 # Since http_method is 'GET'
INADA Naoki0bceb332014-08-20 15:27:52 +0900155 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800156
157 def test_fix_up_parameters_insert(self):
158 parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
159 'POST', self.zoo_root_desc)
160 body = {
161 'description': 'The request body.',
162 'type': 'object',
163 'required': True,
164 '$ref': 'Animal',
165 }
166 self.assertEqual(parameters['body'], body)
167
168 def test_fix_up_parameters_check_body(self):
169 dummy_root_desc = {}
170 no_payload_http_method = 'DELETE'
171 with_payload_http_method = 'PUT'
172
173 invalid_method_desc = {'response': 'Who cares'}
174 valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
175
176 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
177 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900178 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800179
180 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
181 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900182 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800183
184 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
185 with_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900186 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800187
188 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
189 with_payload_http_method)
190 body = {
191 'description': 'The request body.',
192 'type': 'object',
193 'required': True,
194 'key1': 'value1',
195 'key2': 'value2',
196 }
197 self.assertEqual(parameters['body'], body)
198
199 def _base_fix_up_method_description_test(
200 self, method_desc, initial_parameters, final_parameters,
201 final_accept, final_max_size, final_media_path_url):
202 fake_root_desc = {'rootUrl': 'http://root/',
203 'servicePath': 'fake/'}
204 fake_path_url = 'fake-path/'
205
206 accept, max_size, media_path_url = _fix_up_media_upload(
207 method_desc, fake_root_desc, fake_path_url, initial_parameters)
208 self.assertEqual(accept, final_accept)
209 self.assertEqual(max_size, final_max_size)
210 self.assertEqual(media_path_url, final_media_path_url)
211 self.assertEqual(initial_parameters, final_parameters)
212
213 def test_fix_up_media_upload_no_initial_invalid(self):
214 invalid_method_desc = {'response': 'Who cares'}
215 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
216 [], 0, None)
217
218 def test_fix_up_media_upload_no_initial_valid_minimal(self):
219 valid_method_desc = {'mediaUpload': {'accept': []}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700220 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
221 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800222 self._base_fix_up_method_description_test(
223 valid_method_desc, {}, final_parameters, [], 0,
224 'http://root/upload/fake/fake-path/')
225
226 def test_fix_up_media_upload_no_initial_valid_full(self):
227 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700228 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
229 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800230 ten_gb = 10 * 2**30
231 self._base_fix_up_method_description_test(
232 valid_method_desc, {}, final_parameters, ['*/*'],
233 ten_gb, 'http://root/upload/fake/fake-path/')
234
235 def test_fix_up_media_upload_with_initial_invalid(self):
236 invalid_method_desc = {'response': 'Who cares'}
237 initial_parameters = {'body': {}}
238 self._base_fix_up_method_description_test(
239 invalid_method_desc, initial_parameters,
240 initial_parameters, [], 0, None)
241
242 def test_fix_up_media_upload_with_initial_valid_minimal(self):
243 valid_method_desc = {'mediaUpload': {'accept': []}}
244 initial_parameters = {'body': {}}
245 final_parameters = {'body': {'required': False},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700246 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
247 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800248 self._base_fix_up_method_description_test(
249 valid_method_desc, initial_parameters, final_parameters, [], 0,
250 'http://root/upload/fake/fake-path/')
251
252 def test_fix_up_media_upload_with_initial_valid_full(self):
253 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
254 initial_parameters = {'body': {}}
255 final_parameters = {'body': {'required': False},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700256 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
257 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800258 ten_gb = 10 * 2**30
259 self._base_fix_up_method_description_test(
260 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
261 ten_gb, 'http://root/upload/fake/fake-path/')
262
263 def test_fix_up_method_description_get(self):
264 result = _fix_up_method_description(self.zoo_get_method_desc,
265 self.zoo_root_desc)
266 path_url = 'query'
267 http_method = 'GET'
268 method_id = 'bigquery.query'
269 accept = []
INADA Naoki0bceb332014-08-20 15:27:52 +0900270 max_size = 0
Daniel Hermesc2113242013-02-27 10:16:13 -0800271 media_path_url = None
272 self.assertEqual(result, (path_url, http_method, method_id, accept,
273 max_size, media_path_url))
274
275 def test_fix_up_method_description_insert(self):
276 result = _fix_up_method_description(self.zoo_insert_method_desc,
277 self.zoo_root_desc)
278 path_url = 'animals'
279 http_method = 'POST'
280 method_id = 'zoo.animals.insert'
281 accept = ['image/png']
INADA Naoki0bceb332014-08-20 15:27:52 +0900282 max_size = 1024
Daniel Hermesc2113242013-02-27 10:16:13 -0800283 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
284 self.assertEqual(result, (path_url, http_method, method_id, accept,
285 max_size, media_path_url))
286
Craig Citro7ee535d2015-02-23 10:11:14 -0800287 def test_urljoin(self):
288 # We want to exhaustively test various URL combinations.
289 simple_bases = ['https://www.googleapis.com', 'https://www.googleapis.com/']
290 long_urls = ['foo/v1/bar:custom?alt=json', '/foo/v1/bar:custom?alt=json']
291
292 long_bases = [
293 'https://www.googleapis.com/foo/v1',
294 'https://www.googleapis.com/foo/v1/',
295 ]
296 simple_urls = ['bar:custom?alt=json', '/bar:custom?alt=json']
297
298 final_url = 'https://www.googleapis.com/foo/v1/bar:custom?alt=json'
299 for base, url in itertools.product(simple_bases, long_urls):
300 self.assertEqual(final_url, _urljoin(base, url))
301 for base, url in itertools.product(long_bases, simple_urls):
302 self.assertEqual(final_url, _urljoin(base, url))
303
304
Daniel Hermes954e1242013-02-28 09:28:37 -0800305 def test_ResourceMethodParameters_zoo_get(self):
306 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
307
308 param_types = {'a': 'any',
309 'b': 'boolean',
310 'e': 'string',
311 'er': 'string',
312 'i': 'integer',
313 'n': 'number',
314 'o': 'object',
315 'q': 'string',
316 'rr': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900317 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800318 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
319 self.assertEqual(parameters.required_params, [])
320 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
321 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
322 self.assertEqual(sorted(parameters.query_params),
323 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
324 self.assertEqual(parameters.path_params, set())
325 self.assertEqual(parameters.param_types, param_types)
326 enum_params = {'e': ['foo', 'bar'],
327 'er': ['one', 'two', 'three']}
328 self.assertEqual(parameters.enum_params, enum_params)
329
330 def test_ResourceMethodParameters_zoo_animals_patch(self):
331 method_desc = self.zoo_animals_resource['methods']['patch']
332 parameters = ResourceMethodParameters(method_desc)
333
334 param_types = {'name': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900335 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800336 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
337 self.assertEqual(parameters.required_params, ['name'])
338 self.assertEqual(parameters.repeated_params, [])
339 self.assertEqual(parameters.pattern_params, {})
340 self.assertEqual(parameters.query_params, [])
341 self.assertEqual(parameters.path_params, set(['name']))
342 self.assertEqual(parameters.param_types, param_types)
343 self.assertEqual(parameters.enum_params, {})
344
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400345
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500346class DiscoveryErrors(unittest.TestCase):
347
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400348 def test_tests_should_be_run_with_strict_positional_enforcement(self):
349 try:
350 plus = build('plus', 'v1', None)
351 self.fail("should have raised a TypeError exception over missing http=.")
352 except TypeError:
353 pass
354
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500355 def test_failed_to_parse_discovery_json(self):
356 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
357 try:
Takashi Matsuo30125122015-08-19 11:42:32 -0700358 plus = build('plus', 'v1', http=self.http, cache_discovery=False)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500359 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500360 except InvalidJsonError:
361 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500362
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700363 def test_unknown_api_name_or_version(self):
364 http = HttpMockSequence([
365 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Ethan Bao12b7cd32016-03-14 14:25:10 -0700366 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700367 ])
368 with self.assertRaises(UnknownApiNameOrVersion):
369 plus = build('plus', 'v1', http=http, cache_discovery=False)
370
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800371 def test_credentials_and_http_mutually_exclusive(self):
372 http = HttpMock(datafile('plus.json'), {'status': '200'})
373 with self.assertRaises(ValueError):
374 build(
375 'plus', 'v1', http=http, credentials=mock.sentinel.credentials)
376
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500377
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100378class DiscoveryFromDocument(unittest.TestCase):
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800379 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400380
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100381 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400382 discovery = open(datafile('plus.json')).read()
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800383 plus = build_from_document(
384 discovery, base="https://www.googleapis.com/",
385 credentials=self.MOCK_CREDENTIALS)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500386 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500387 self.assertTrue(hasattr(plus, 'activities'))
388
389 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400390 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700391 discovery = json.loads(discovery)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800392 plus = build_from_document(
393 discovery, base="https://www.googleapis.com/",
394 credentials=self.MOCK_CREDENTIALS)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500395 self.assertTrue(plus is not None)
396 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400397
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100398 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400399 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400400
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100401 base = "https://www.example.com/"
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800402 plus = build_from_document(
403 discovery, base=base, credentials=self.MOCK_CREDENTIALS)
Joe Gregorioa2838152012-07-16 11:52:17 -0400404 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100405
Igor Maravić22435292017-01-19 22:28:22 +0100406 def test_building_with_optional_http_with_authorization(self):
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700407 discovery = open(datafile('plus.json')).read()
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800408 plus = build_from_document(
409 discovery, base="https://www.googleapis.com/",
410 credentials=self.MOCK_CREDENTIALS)
Igor Maravić22435292017-01-19 22:28:22 +0100411
412 # plus service requires Authorization, hence we expect to see AuthorizedHttp object here
413 self.assertIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
414 self.assertIsInstance(plus._http.http, httplib2.Http)
415 self.assertIsInstance(plus._http.http.timeout, int)
416 self.assertGreater(plus._http.http.timeout, 0)
417
418 def test_building_with_optional_http_with_no_authorization(self):
419 discovery = open(datafile('plus.json')).read()
420 # Cleanup auth field, so we would use plain http client
421 discovery = json.loads(discovery)
422 discovery['auth'] = {}
423 discovery = json.dumps(discovery)
424
425 plus = build_from_document(
426 discovery, base="https://www.googleapis.com/",
427 credentials=self.MOCK_CREDENTIALS)
428 # plus service requires Authorization
429 self.assertIsInstance(plus._http, httplib2.Http)
430 self.assertIsInstance(plus._http.timeout, int)
431 self.assertGreater(plus._http.timeout, 0)
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700432
433 def test_building_with_explicit_http(self):
434 http = HttpMock()
435 discovery = open(datafile('plus.json')).read()
436 plus = build_from_document(
437 discovery, base="https://www.googleapis.com/", http=http)
438 self.assertEquals(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100439
Jon Wayne Parrott068eb352017-02-08 10:13:06 -0800440 def test_building_with_developer_key_skips_adc(self):
441 discovery = open(datafile('plus.json')).read()
442 plus = build_from_document(
443 discovery, base="https://www.googleapis.com/", developerKey='123')
444 self.assertIsInstance(plus._http, httplib2.Http)
445 # It should not be an AuthorizedHttp, because that would indicate that
446 # application default credentials were used.
447 self.assertNotIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
448
449
Joe Gregorioa98733f2011-09-16 10:12:28 -0400450class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400451 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700452 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400453
Joe Gregorio583d9e42011-09-16 15:54:15 -0400454 def tearDown(self):
455 os.environ = self.old_environ
456
457 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400458 # build() will raise an HttpError on a 400, use this to pick the request uri
459 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400460 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400461 try:
462 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400463 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400464 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400465 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400466 discoveryServiceUrl='http://example.com')
467 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900468 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400469 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400470
Joe Gregorio583d9e42011-09-16 15:54:15 -0400471 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400472 # build() will raise an HttpError on a 400, use this to pick the request uri
473 # out of the raised exception.
474 try:
475 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400476 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400477 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400478 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400479 discoveryServiceUrl='http://example.com')
480 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900481 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400482 self.assertEqual(e.uri, 'http://example.com')
483
Ethan Bao12b7cd32016-03-14 14:25:10 -0700484 def test_discovery_loading_from_v2_discovery_uri(self):
485 http = HttpMockSequence([
486 ({'status': '404'}, 'Not found'),
487 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
488 ])
489 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
490 self.assertTrue(hasattr(zoo, 'animals'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400491
Takashi Matsuo30125122015-08-19 11:42:32 -0700492class DiscoveryFromAppEngineCache(unittest.TestCase):
493 def test_appengine_memcache(self):
494 # Hack module import
495 self.orig_import = __import__
496 self.mocked_api = mock.MagicMock()
497
eesheeshc6425a02016-02-12 15:07:06 +0000498 def import_mock(name, *args, **kwargs):
Takashi Matsuo30125122015-08-19 11:42:32 -0700499 if name == 'google.appengine.api':
500 return self.mocked_api
eesheeshc6425a02016-02-12 15:07:06 +0000501 return self.orig_import(name, *args, **kwargs)
Takashi Matsuo30125122015-08-19 11:42:32 -0700502
503 import_fullname = '__builtin__.__import__'
504 if sys.version_info[0] >= 3:
505 import_fullname = 'builtins.__import__'
506
507 with mock.patch(import_fullname, side_effect=import_mock):
508 namespace = 'google-api-client'
509 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
510
511 self.mocked_api.memcache.get.return_value = None
512
513 plus = build('plus', 'v1', http=self.http)
514
515 # memcache.get is called once
516 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
517 self.mocked_api.memcache.get.assert_called_once_with(url,
518 namespace=namespace)
519
520 # memcache.set is called once
521 with open(datafile('plus.json')) as f:
522 content = f.read()
523 self.mocked_api.memcache.set.assert_called_once_with(
524 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace)
525
526 # Returns the cached content this time.
527 self.mocked_api.memcache.get.return_value = content
528
529 # Make sure the contents are returned from the cache.
530 # (Otherwise it should through an error)
531 self.http = HttpMock(None, {'status': '200'})
532
533 plus = build('plus', 'v1', http=self.http)
534
535 # memcache.get is called twice
536 self.mocked_api.memcache.get.assert_has_calls(
537 [mock.call(url, namespace=namespace),
538 mock.call(url, namespace=namespace)])
539
540 # memcahce.set is called just once
541 self.mocked_api.memcache.set.assert_called_once_with(
542 url, content, time=DISCOVERY_DOC_MAX_AGE,namespace=namespace)
543
544
545class DictCache(Cache):
546 def __init__(self):
547 self.d = {}
548 def get(self, url):
549 return self.d.get(url, None)
550 def set(self, url, content):
551 self.d[url] = content
552 def contains(self, url):
553 return url in self.d
554
555
556class DiscoveryFromFileCache(unittest.TestCase):
557 def test_file_based_cache(self):
558 cache = mock.Mock(wraps=DictCache())
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -0700559 with mock.patch('googleapiclient.discovery_cache.autodetect',
560 return_value=cache):
Takashi Matsuo30125122015-08-19 11:42:32 -0700561 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
562
563 plus = build('plus', 'v1', http=self.http)
564
565 # cache.get is called once
566 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
567 cache.get.assert_called_once_with(url)
568
569 # cache.set is called once
570 with open(datafile('plus.json')) as f:
571 content = f.read()
572 cache.set.assert_called_once_with(url, content)
573
574 # Make sure there is a cache entry for the plus v1 discovery doc.
575 self.assertTrue(cache.contains(url))
576
577 # Make sure the contents are returned from the cache.
578 # (Otherwise it should through an error)
579 self.http = HttpMock(None, {'status': '200'})
580
581 plus = build('plus', 'v1', http=self.http)
582
583 # cache.get is called twice
584 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
585
586 # cahce.set is called just once
587 cache.set.assert_called_once_with(url, content)
588
589
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400590class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400591
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400592 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500593 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400594 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400595
596 # Missing required parameters
597 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500598 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400599 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900600 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400601 self.assertTrue('Missing' in str(e))
602
Joe Gregorio2467afa2012-06-20 12:21:25 -0400603 # Missing required parameters even if supplied as None.
604 try:
605 plus.activities().list(collection=None, userId=None)
606 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900607 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400608 self.assertTrue('Missing' in str(e))
609
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400610 # Parameter doesn't match regex
611 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500612 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400613 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900614 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500615 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400616
617 # Unexpected parameter
618 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500619 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400620 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900621 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400622 self.assertTrue('unexpected' in str(e))
623
Joe Gregoriobee86832011-02-22 10:00:19 -0500624 def _check_query_types(self, request):
Pat Ferated5b61bd2015-03-03 16:04:11 -0800625 parsed = urlparse(request.uri)
Joe Gregoriobee86832011-02-22 10:00:19 -0500626 q = parse_qs(parsed[4])
627 self.assertEqual(q['q'], ['foo'])
628 self.assertEqual(q['i'], ['1'])
629 self.assertEqual(q['n'], ['1.0'])
630 self.assertEqual(q['b'], ['false'])
631 self.assertEqual(q['a'], ['[1, 2, 3]'])
632 self.assertEqual(q['o'], ['{\'a\': 1}'])
633 self.assertEqual(q['e'], ['bar'])
634
635 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400636 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400637 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500638
Joe Gregorioa98733f2011-09-16 10:12:28 -0400639 request = zoo.query(
640 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 -0500641 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400642 request = zoo.query(
643 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500644 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500645
Joe Gregorioa98733f2011-09-16 10:12:28 -0400646 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800647 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 -0500648
649 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800650 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
651 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500652 self._check_query_types(request)
653
Craig Citro1e742822012-03-01 12:59:22 -0800654 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500655 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800656
Joe Gregorio13217952011-02-22 15:37:38 -0500657 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400658 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400659 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400660 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500661
Pat Ferated5b61bd2015-03-03 16:04:11 -0800662 parsed = urlparse(request.uri)
Joe Gregorioca876e42011-02-22 19:39:42 -0500663 q = parse_qs(parsed[4])
664 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400665 self.assertEqual(q['fields'], ['description'])
666
Joe Gregorio2467afa2012-06-20 12:21:25 -0400667 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400668 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400669 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400670 request = zoo.query(trace=None, fields='description')
671
Pat Ferated5b61bd2015-03-03 16:04:11 -0800672 parsed = urlparse(request.uri)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400673 q = parse_qs(parsed[4])
674 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400675
Joe Gregorioe08a1662011-12-07 09:48:22 -0500676 def test_model_added_query_parameters(self):
677 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400678 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500679 request = zoo.animals().get(name='Lion')
680
Pat Ferated5b61bd2015-03-03 16:04:11 -0800681 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500682 q = parse_qs(parsed[4])
683 self.assertEqual(q['alt'], ['json'])
684 self.assertEqual(request.headers['accept'], 'application/json')
685
686 def test_fallback_to_raw_model(self):
687 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400688 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500689 request = zoo.animals().getmedia(name='Lion')
690
Pat Ferated5b61bd2015-03-03 16:04:11 -0800691 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500692 q = parse_qs(parsed[4])
693 self.assertTrue('alt' not in q)
694 self.assertEqual(request.headers['accept'], '*/*')
695
Joe Gregoriof4153422011-03-18 22:45:18 -0400696 def test_patch(self):
697 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400698 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400699 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
700
701 self.assertEqual(request.method, 'PATCH')
702
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -0400703 def test_batch_request_from_discovery(self):
704 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
705 # zoo defines a batchPath
706 zoo = build('zoo', 'v1', http=self.http)
707 batch_request = zoo.new_batch_http_request()
708 self.assertEqual(batch_request._batch_uri,
709 "https://www.googleapis.com/batchZoo")
710
711 def test_batch_request_from_default(self):
712 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
713 # plus does not define a batchPath
714 plus = build('plus', 'v1', http=self.http)
715 batch_request = plus.new_batch_http_request()
716 self.assertEqual(batch_request._batch_uri,
717 "https://www.googleapis.com/batch")
718
Joe Gregoriof4153422011-03-18 22:45:18 -0400719 def test_tunnel_patch(self):
720 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400721 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400722 ({'status': '200'}, 'echo_request_headers_as_json'),
723 ])
724 http = tunnel_patch(http)
Takashi Matsuo30125122015-08-19 11:42:32 -0700725 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400726 resp = zoo.animals().patch(
727 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400728
729 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500730
Joe Gregorio7b70f432011-11-09 10:18:51 -0500731 def test_plus_resources(self):
732 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400733 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500734 self.assertTrue(getattr(plus, 'activities'))
735 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400736
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800737 def test_oauth2client_credentials(self):
738 credentials = mock.Mock(spec=GoogleCredentials)
739 credentials.create_scoped_required.return_value = False
Orest Bolohane92c9002014-05-30 11:15:43 -0700740
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800741 discovery = open(datafile('plus.json')).read()
742 service = build_from_document(discovery, credentials=credentials)
743 self.assertEqual(service._http, credentials.authorize.return_value)
Orest Bolohane92c9002014-05-30 11:15:43 -0700744
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800745 def test_google_auth_credentials(self):
746 credentials = mock.Mock(spec=google.auth.credentials.Credentials)
747 discovery = open(datafile('plus.json')).read()
748 service = build_from_document(discovery, credentials=credentials)
749
750 self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp)
751 self.assertEqual(service._http.credentials, credentials)
752
753 def test_no_scopes_no_credentials(self):
754 # Zoo doesn't have scopes
755 discovery = open(datafile('zoo.json')).read()
756 service = build_from_document(discovery)
757 # Should be an ordinary httplib2.Http instance and not AuthorizedHttp.
758 self.assertIsInstance(service._http, httplib2.Http)
Orest Bolohane92c9002014-05-30 11:15:43 -0700759
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400760 def test_full_featured(self):
761 # Zoo should exercise all discovery facets
762 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500763 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400764 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400765 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500766
Joe Gregoriof4153422011-03-18 22:45:18 -0400767 request = zoo.animals().list(name='bat', projection="full")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800768 parsed = urlparse(request.uri)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400769 q = parse_qs(parsed[4])
770 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400771 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400772
Joe Gregorio3fada332011-01-07 17:07:45 -0500773 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500774 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400775 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500776 self.assertTrue(getattr(zoo, 'animals'))
777 request = zoo.my().favorites().list(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800778 parsed = urlparse(request.uri)
Joe Gregorio3fada332011-01-07 17:07:45 -0500779 q = parse_qs(parsed[4])
780 self.assertEqual(q['max-results'], ['5'])
781
Pat Feratec6050872015-03-03 18:24:59 -0800782 @unittest.skipIf(six.PY3, 'print is not a reserved name in Python 3')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400783 def test_methods_with_reserved_names(self):
784 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400785 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400786 self.assertTrue(getattr(zoo, 'animals'))
787 request = zoo.global_().print_().assert_(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800788 parsed = urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400789 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400790
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500791 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500792 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400793 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500794 self.assertTrue(getattr(zoo, 'query'))
795 request = zoo.query(q="foo")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800796 parsed = urlparse(request.uri)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500797 q = parse_qs(parsed[4])
798 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400799
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400800 def test_simple_media_uploads(self):
801 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400802 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400803 doc = getattr(zoo.animals().insert, '__doc__')
804 self.assertTrue('media_body' in doc)
805
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400806 def test_simple_media_upload_no_max_size_provided(self):
807 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400808 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400809 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
810 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800811 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400812
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400813 def test_simple_media_raise_correct_exceptions(self):
814 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400815 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400816
817 try:
818 zoo.animals().insert(media_body=datafile('smiley.png'))
819 self.fail("should throw exception if media is too large.")
820 except MediaUploadSizeError:
821 pass
822
823 try:
824 zoo.animals().insert(media_body=datafile('small.jpg'))
825 self.fail("should throw exception if mimetype is unacceptable.")
826 except UnacceptableMimeTypeError:
827 pass
828
829 def test_simple_media_good_upload(self):
830 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400831 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400832
833 request = zoo.animals().insert(media_body=datafile('small.png'))
834 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800835 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500836 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400837 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500838 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400839
Brian J. Watson38051ac2016-10-25 07:53:08 -0700840 def test_simple_media_unknown_mimetype(self):
841 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
842 zoo = build('zoo', 'v1', http=self.http)
843
844 try:
845 zoo.animals().insert(media_body=datafile('small-png'))
846 self.fail("should throw exception if mimetype is unknown.")
847 except UnknownFileType:
848 pass
849
850 request = zoo.animals().insert(media_body=datafile('small-png'),
851 media_mime_type='image/png')
852 self.assertEquals('image/png', request.headers['content-type'])
853 self.assertEquals(b'PNG', request.body[1:4])
854 assertUrisEqual(self,
855 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
856 request.uri)
857
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400858 def test_multipart_media_raise_correct_exceptions(self):
859 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400860 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400861
862 try:
863 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
864 self.fail("should throw exception if media is too large.")
865 except MediaUploadSizeError:
866 pass
867
868 try:
869 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
870 self.fail("should throw exception if mimetype is unacceptable.")
871 except UnacceptableMimeTypeError:
872 pass
873
874 def test_multipart_media_good_upload(self):
875 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400876 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400877
878 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400879 self.assertTrue(request.headers['content-type'].startswith(
880 'multipart/related'))
Phil Ruffwind26178fc2015-10-13 19:00:33 -0400881 with open(datafile('small.png'), 'rb') as f:
882 contents = f.read()
883 boundary = re.match(b'--=+([^=]+)', request.body).group(1)
884 self.assertEqual(
885 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
886 b'--===============' + boundary + b'==\n' +
887 b'Content-Type: application/json\n' +
888 b'MIME-Version: 1.0\n\n' +
889 b'{"data": {}}\n' +
890 b'--===============' + boundary + b'==\n' +
891 b'Content-Type: image/png\n' +
892 b'MIME-Version: 1.0\n' +
893 b'Content-Transfer-Encoding: binary\n\n' +
894 contents +
895 b'\n--===============' + boundary + b'==--')
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500896 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400897 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500898 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400899
900 def test_media_capable_method_without_media(self):
901 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400902 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400903
904 request = zoo.animals().insert(body={})
905 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400906
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500907 def test_resumable_multipart_media_good_upload(self):
908 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400909 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500910
911 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
912 request = zoo.animals().insert(media_body=media_upload, body={})
913 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500914 'application/json'))
915 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500916 self.assertEquals(media_upload, request.resumable)
917
918 self.assertEquals('image/png', request.resumable.mimetype())
919
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500920 self.assertNotEquals(request.body, None)
921 self.assertEquals(request.resumable_uri, None)
922
923 http = HttpMockSequence([
924 ({'status': '200',
925 'location': 'http://upload.example.com'}, ''),
926 ({'status': '308',
Matt Carroll94a53942016-12-20 13:56:43 -0800927 'location': 'http://upload.example.com/2'}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500928 ({'status': '308',
929 'location': 'http://upload.example.com/3',
Matt Carroll94a53942016-12-20 13:56:43 -0800930 'range': '0-12'}, ''),
931 ({'status': '308',
932 'location': 'http://upload.example.com/4',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500933 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500934 ({'status': '200'}, '{"foo": "bar"}'),
935 ])
936
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400937 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500938 self.assertEquals(None, body)
939 self.assertTrue(isinstance(status, MediaUploadProgress))
Matt Carroll94a53942016-12-20 13:56:43 -0800940 self.assertEquals(0, status.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500941
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500942 # Two requests should have been made and the resumable_uri should have been
943 # updated for each one.
944 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
Matt Carroll94a53942016-12-20 13:56:43 -0800945 self.assertEquals(media_upload, request.resumable)
946 self.assertEquals(0, request.resumable_progress)
947
948 # This next chuck call should upload the first chunk
949 status, body = request.next_chunk(http=http)
950 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500951 self.assertEquals(media_upload, request.resumable)
952 self.assertEquals(13, request.resumable_progress)
953
Matt Carroll94a53942016-12-20 13:56:43 -0800954 # This call will upload the next chunk
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400955 status, body = request.next_chunk(http=http)
Matt Carroll94a53942016-12-20 13:56:43 -0800956 self.assertEquals(request.resumable_uri, 'http://upload.example.com/4')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500957 self.assertEquals(media_upload.size()-1, request.resumable_progress)
958 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500959
960 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400961 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500962 self.assertEquals(body, {"foo": "bar"})
963 self.assertEquals(status, None)
964
965
966 def test_resumable_media_good_upload(self):
967 """Not a multipart upload."""
968 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400969 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500970
971 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
972 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500973 self.assertEquals(media_upload, request.resumable)
974
975 self.assertEquals('image/png', request.resumable.mimetype())
976
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500977 self.assertEquals(request.body, None)
978 self.assertEquals(request.resumable_uri, None)
979
980 http = HttpMockSequence([
981 ({'status': '200',
982 'location': 'http://upload.example.com'}, ''),
983 ({'status': '308',
984 'location': 'http://upload.example.com/2',
985 'range': '0-12'}, ''),
986 ({'status': '308',
987 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500988 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500989 ({'status': '200'}, '{"foo": "bar"}'),
990 ])
991
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400992 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500993 self.assertEquals(None, body)
994 self.assertTrue(isinstance(status, MediaUploadProgress))
995 self.assertEquals(13, status.resumable_progress)
996
997 # Two requests should have been made and the resumable_uri should have been
998 # updated for each one.
999 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
1000
1001 self.assertEquals(media_upload, request.resumable)
1002 self.assertEquals(13, request.resumable_progress)
1003
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001004 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001005 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -05001006 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001007 self.assertEquals(request.body, None)
1008
1009 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001010 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001011 self.assertEquals(body, {"foo": "bar"})
1012 self.assertEquals(status, None)
1013
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001014 def test_resumable_media_good_upload_from_execute(self):
1015 """Not a multipart upload."""
1016 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001017 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001018
1019 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1020 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -05001021 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -04001022 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -05001023 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001024
1025 http = HttpMockSequence([
1026 ({'status': '200',
1027 'location': 'http://upload.example.com'}, ''),
1028 ({'status': '308',
1029 'location': 'http://upload.example.com/2',
1030 'range': '0-12'}, ''),
1031 ({'status': '308',
1032 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -05001033 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001034 ({'status': '200'}, '{"foo": "bar"}'),
1035 ])
1036
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001037 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001038 self.assertEquals(body, {"foo": "bar"})
1039
1040 def test_resumable_media_fail_unknown_response_code_first_request(self):
1041 """Not a multipart upload."""
1042 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001043 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001044
1045 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1046 request = zoo.animals().insert(media_body=media_upload, body=None)
1047
1048 http = HttpMockSequence([
1049 ({'status': '400',
1050 'location': 'http://upload.example.com'}, ''),
1051 ])
1052
Joe Gregoriobaf04802013-03-01 12:27:06 -05001053 try:
1054 request.execute(http=http)
1055 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +09001056 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -05001057 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001058
1059 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
1060 """Not a multipart upload."""
1061 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001062 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001063
1064 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1065 request = zoo.animals().insert(media_body=media_upload, body=None)
1066
1067 http = HttpMockSequence([
1068 ({'status': '200',
1069 'location': 'http://upload.example.com'}, ''),
1070 ({'status': '400'}, ''),
1071 ])
1072
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001073 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001074 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001075
Joe Gregorio910b9b12012-06-12 09:36:30 -04001076 http = HttpMockSequence([
1077 ({'status': '308',
1078 'range': '0-5'}, ''),
1079 ({'status': '308',
1080 'range': '0-6'}, ''),
1081 ])
1082
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001083 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001084 self.assertEquals(status.resumable_progress, 7,
1085 'Should have first checked length and then tried to PUT more.')
1086 self.assertFalse(request._in_error_state)
1087
1088 # Put it back in an error state.
1089 http = HttpMockSequence([
1090 ({'status': '400'}, ''),
1091 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001092 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001093 self.assertTrue(request._in_error_state)
1094
1095 # Pretend the last request that 400'd actually succeeded.
1096 http = HttpMockSequence([
1097 ({'status': '200'}, '{"foo": "bar"}'),
1098 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001099 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001100 self.assertEqual(body, {'foo': 'bar'})
1101
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001102 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1103 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1104 zoo = build('zoo', 'v1', http=self.http)
1105
Pat Ferateed9affd2015-03-03 16:03:15 -08001106 # Set up a seekable stream and try to upload in single chunk.
Pat Ferate2b140222015-03-03 18:05:11 -08001107 fd = BytesIO(b'01234"56789"')
Pat Ferateed9affd2015-03-03 16:03:15 -08001108 media_upload = MediaIoBaseUpload(
1109 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001110
Pat Ferateed9affd2015-03-03 16:03:15 -08001111 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001112
Pat Ferateed9affd2015-03-03 16:03:15 -08001113 # The single chunk fails, restart at the right point.
1114 http = HttpMockSequence([
1115 ({'status': '200',
1116 'location': 'http://upload.example.com'}, ''),
1117 ({'status': '308',
1118 'location': 'http://upload.example.com/2',
1119 'range': '0-4'}, ''),
1120 ({'status': '200'}, 'echo_request_body'),
1121 ])
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001122
Pat Ferateed9affd2015-03-03 16:03:15 -08001123 body = request.execute(http=http)
1124 self.assertEqual('56789', body)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001125
1126 def test_media_io_base_stream_chunksize_resume(self):
1127 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1128 zoo = build('zoo', 'v1', http=self.http)
1129
Pat Ferateed9affd2015-03-03 16:03:15 -08001130 # Set up a seekable stream and try to upload in chunks.
Pat Ferate2b140222015-03-03 18:05:11 -08001131 fd = BytesIO(b'0123456789')
Pat Ferateed9affd2015-03-03 16:03:15 -08001132 media_upload = MediaIoBaseUpload(
1133 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
1134
1135 request = zoo.animals().insert(media_body=media_upload, body=None)
1136
1137 # The single chunk fails, pull the content sent out of the exception.
1138 http = HttpMockSequence([
1139 ({'status': '200',
1140 'location': 'http://upload.example.com'}, ''),
1141 ({'status': '400'}, 'echo_request_body'),
1142 ])
1143
Joe Gregorio5c120db2012-08-23 09:13:55 -04001144 try:
Pat Ferateed9affd2015-03-03 16:03:15 -08001145 body = request.execute(http=http)
1146 except HttpError as e:
Pat Ferate2b140222015-03-03 18:05:11 -08001147 self.assertEqual(b'01234', e.content)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001148
Joe Gregorio910b9b12012-06-12 09:36:30 -04001149 def test_resumable_media_handle_uploads_of_unknown_size(self):
1150 http = HttpMockSequence([
1151 ({'status': '200',
1152 'location': 'http://upload.example.com'}, ''),
1153 ({'status': '200'}, 'echo_request_headers_as_json'),
1154 ])
1155
1156 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001157 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001158
Joe Gregorio910b9b12012-06-12 09:36:30 -04001159 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001160 class IoBaseUnknownLength(MediaUpload):
1161 def chunksize(self):
1162 return 10
1163
1164 def mimetype(self):
1165 return 'image/png'
1166
1167 def size(self):
1168 return None
1169
1170 def resumable(self):
1171 return True
1172
1173 def getbytes(self, begin, length):
1174 return '0123456789'
1175
1176 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -04001177
1178 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001179 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001180 self.assertEqual(body, {
1181 'Content-Range': 'bytes 0-9/*',
1182 'Content-Length': '10',
1183 })
Joe Gregorio44454e42012-06-15 08:38:53 -04001184
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001185 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1186 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1187 zoo = build('zoo', 'v1', http=self.http)
1188
1189 class IoBaseHasStream(MediaUpload):
1190 def chunksize(self):
1191 return 10
1192
1193 def mimetype(self):
1194 return 'image/png'
1195
1196 def size(self):
1197 return None
1198
1199 def resumable(self):
1200 return True
1201
1202 def getbytes(self, begin, length):
1203 return '0123456789'
1204
1205 def has_stream(self):
1206 return True
1207
1208 def stream(self):
1209 raise NotImplementedError()
1210
1211 upload = IoBaseHasStream()
1212
1213 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001214
1215 sys.version_info = (2, 6, 5, 'final', 0)
1216
1217 request = zoo.animals().insert(media_body=upload, body=None)
1218
1219 # This should raise an exception because stream() will be called.
1220 http = HttpMockSequence([
1221 ({'status': '200',
1222 'location': 'http://upload.example.com'}, ''),
1223 ({'status': '200'}, 'echo_request_headers_as_json'),
1224 ])
1225
1226 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
1227
1228 sys.version_info = orig_version
1229
Joe Gregorio44454e42012-06-15 08:38:53 -04001230 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1231 http = HttpMockSequence([
1232 ({'status': '200',
1233 'location': 'http://upload.example.com'}, ''),
1234 ({'status': '200'}, 'echo_request_headers_as_json'),
1235 ])
1236
1237 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001238 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001239
Pat Ferate2b140222015-03-03 18:05:11 -08001240 fd = BytesIO(b'data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001241
1242 # Create an upload that doesn't know the full size of the media.
1243 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001244 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001245
1246 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001247 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001248 self.assertEqual(body, {
1249 'Content-Range': 'bytes 0-13/14',
1250 'Content-Length': '14',
1251 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001252
1253 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1254 http = HttpMockSequence([
1255 ({'status': '200',
1256 'location': 'http://upload.example.com'}, ''),
1257 ({'status': '400'}, ''),
1258 ])
1259
1260 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001261 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001262
1263 # Create an upload that doesn't know the full size of the media.
Pat Ferate2b140222015-03-03 18:05:11 -08001264 fd = BytesIO(b'data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001265
1266 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001267 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001268
1269 request = zoo.animals().insert(media_body=upload, body=None)
1270
1271 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001272 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001273
1274 http = HttpMockSequence([
1275 ({'status': '400',
1276 'range': '0-5'}, 'echo_request_headers_as_json'),
1277 ])
1278 try:
1279 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001280 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001281 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001282 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001283 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001284 'content-length': '0'
1285 }
INADA Naoki09157612015-03-25 01:51:03 +09001286 self.assertEqual(expected, json.loads(e.content.decode('utf-8')),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001287 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001288
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001289 def test_pickle(self):
1290 sorted_resource_keys = ['_baseUrl',
1291 '_developerKey',
1292 '_dynamic_attrs',
1293 '_http',
1294 '_model',
1295 '_requestBuilder',
1296 '_resourceDesc',
1297 '_rootDesc',
1298 '_schema',
1299 'animals',
1300 'global_',
1301 'load',
1302 'loadNoTemplate',
1303 'my',
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001304 'new_batch_http_request',
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001305 'query',
1306 'scopedAnimals']
1307
1308 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1309 zoo = build('zoo', 'v1', http=http)
1310 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1311
1312 pickled_zoo = pickle.dumps(zoo)
1313 new_zoo = pickle.loads(pickled_zoo)
1314 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1315 self.assertTrue(hasattr(new_zoo, 'animals'))
1316 self.assertTrue(callable(new_zoo.animals))
1317 self.assertTrue(hasattr(new_zoo, 'global_'))
1318 self.assertTrue(callable(new_zoo.global_))
1319 self.assertTrue(hasattr(new_zoo, 'load'))
1320 self.assertTrue(callable(new_zoo.load))
1321 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1322 self.assertTrue(callable(new_zoo.loadNoTemplate))
1323 self.assertTrue(hasattr(new_zoo, 'my'))
1324 self.assertTrue(callable(new_zoo.my))
1325 self.assertTrue(hasattr(new_zoo, 'query'))
1326 self.assertTrue(callable(new_zoo.query))
1327 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1328 self.assertTrue(callable(new_zoo.scopedAnimals))
1329
Joe Gregorio003b6e42013-02-13 15:42:19 -05001330 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001331 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1332 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1333 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1334 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1335 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1336 # _http, _model and _schema won't be equal since we will get new
1337 # instances upon un-pickling
1338
1339 def _dummy_zoo_request(self):
1340 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1341 zoo_contents = fh.read()
1342
1343 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1344 {'api': 'zoo', 'apiVersion': 'v1'})
1345 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001346 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1347 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001348
Igor Maravić22435292017-01-19 22:28:22 +01001349 http = build_http()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001350 original_request = http.request
1351 def wrapped_request(uri, method='GET', *args, **kwargs):
1352 if uri == zoo_uri:
1353 return httplib2.Response({'status': '200'}), zoo_contents
1354 return original_request(uri, method=method, *args, **kwargs)
1355 http.request = wrapped_request
1356 return http
1357
1358 def _dummy_token(self):
1359 access_token = 'foo'
1360 client_id = 'some_client_id'
1361 client_secret = 'cOuDdkfjxxnv+'
1362 refresh_token = '1/0/a.df219fjls0'
1363 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001364 user_agent = 'refresh_checker/1.0'
1365 return OAuth2Credentials(
1366 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001367 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001368 user_agent)
1369
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001370 def test_pickle_with_credentials(self):
1371 credentials = self._dummy_token()
1372 http = self._dummy_zoo_request()
1373 http = credentials.authorize(http)
1374 self.assertTrue(hasattr(http.request, 'credentials'))
1375
1376 zoo = build('zoo', 'v1', http=http)
1377 pickled_zoo = pickle.dumps(zoo)
1378 new_zoo = pickle.loads(pickled_zoo)
1379 self.assertEqual(sorted(zoo.__dict__.keys()),
1380 sorted(new_zoo.__dict__.keys()))
1381 new_http = new_zoo._http
1382 self.assertFalse(hasattr(new_http.request, 'credentials'))
1383
Joe Gregorio708388c2012-06-15 13:43:04 -04001384
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001385class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001386
Joe Gregorio3c676f92011-07-25 10:38:14 -04001387 def test_next_successful_none_on_no_next_page_token(self):
1388 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001389 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001390 request = tasks.tasklists().list()
1391 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1392
Son Dinh2a9a2132015-07-23 16:30:56 +00001393 def test_next_successful_none_on_empty_page_token(self):
1394 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
1395 tasks = build('tasks', 'v1', http=self.http)
1396 request = tasks.tasklists().list()
1397 next_request = tasks.tasklists().list_next(
1398 request, {'nextPageToken': ''})
1399 self.assertEqual(None, next_request)
1400
Joe Gregorio3c676f92011-07-25 10:38:14 -04001401 def test_next_successful_with_next_page_token(self):
1402 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001403 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001404 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001405 next_request = tasks.tasklists().list_next(
1406 request, {'nextPageToken': '123abc'})
Pat Ferated5b61bd2015-03-03 16:04:11 -08001407 parsed = list(urlparse(next_request.uri))
Joe Gregorio3c676f92011-07-25 10:38:14 -04001408 q = parse_qs(parsed[4])
1409 self.assertEqual(q['pageToken'][0], '123abc')
1410
Joe Gregorio555f33c2011-08-19 14:56:07 -04001411 def test_next_with_method_with_no_properties(self):
1412 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001413 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001414 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001415
Joe Gregorioa98733f2011-09-16 10:12:28 -04001416
Joe Gregorio708388c2012-06-15 13:43:04 -04001417class MediaGet(unittest.TestCase):
1418
1419 def test_get_media(self):
1420 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001421 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001422 request = zoo.animals().get_media(name='Lion')
1423
Pat Ferated5b61bd2015-03-03 16:04:11 -08001424 parsed = urlparse(request.uri)
Joe Gregorio708388c2012-06-15 13:43:04 -04001425 q = parse_qs(parsed[4])
1426 self.assertEqual(q['alt'], ['media'])
1427 self.assertEqual(request.headers['accept'], '*/*')
1428
1429 http = HttpMockSequence([
1430 ({'status': '200'}, 'standing in for media'),
1431 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001432 response = request.execute(http=http)
INADA Naoki09157612015-03-25 01:51:03 +09001433 self.assertEqual(b'standing in for media', response)
Joe Gregorio708388c2012-06-15 13:43:04 -04001434
1435
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001436if __name__ == '__main__':
1437 unittest.main()