blob: fd1b9ae93fd0fdf8162aec1cfb94b1ac4fc58b52 [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
Thomas Coffee20af04d2017-02-10 15:24:44 -080077from googleapiclient.model import JsonModel
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080078from oauth2client import GOOGLE_TOKEN_URI
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080079from oauth2client.client import OAuth2Credentials, GoogleCredentials
Joe Gregorio79daca02013-03-29 16:25:52 -040080
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -070081try:
82 from oauth2client import util
83except ImportError:
84 from oauth2client import _helpers as util
85
Joe Gregoriodc106fc2012-11-20 14:30:14 -050086import uritemplate
87
Joe Gregoriocb8103d2011-02-11 23:20:52 -050088
89DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
90
Joe Gregorioa98733f2011-09-16 10:12:28 -040091
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050092def assertUrisEqual(testcase, expected, actual):
93 """Test that URIs are the same, up to reordering of query parameters."""
Pat Ferated5b61bd2015-03-03 16:04:11 -080094 expected = urlparse(expected)
95 actual = urlparse(actual)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050096 testcase.assertEqual(expected.scheme, actual.scheme)
97 testcase.assertEqual(expected.netloc, actual.netloc)
98 testcase.assertEqual(expected.path, actual.path)
99 testcase.assertEqual(expected.params, actual.params)
100 testcase.assertEqual(expected.fragment, actual.fragment)
101 expected_query = parse_qs(expected.query)
102 actual_query = parse_qs(actual.query)
INADA Naokid898a372015-03-04 03:52:46 +0900103 for name in list(expected_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500104 testcase.assertEqual(expected_query[name], actual_query[name])
INADA Naokid898a372015-03-04 03:52:46 +0900105 for name in list(actual_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500106 testcase.assertEqual(expected_query[name], actual_query[name])
107
108
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500109def datafile(filename):
110 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400111
112
Joe Gregorio504a17f2012-12-07 14:14:26 -0500113class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800114
Joe Gregorio504a17f2012-12-07 14:14:26 -0500115 def test_retries(self):
John Asmuth864311d2014-04-24 15:46:08 -0400116 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
Joe Gregorio504a17f2012-12-07 14:14:26 -0500117 self.assertEqual(1, httplib2.RETRIES)
118
119
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400120class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800121
122 def setUp(self):
123 with open(datafile('zoo.json'), 'r') as fh:
Craig Citro6ae34d72014-08-18 23:10:09 -0700124 self.zoo_root_desc = json.loads(fh.read())
Daniel Hermesc2113242013-02-27 10:16:13 -0800125 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800126 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
127 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Daniel Hermesc2113242013-02-27 10:16:13 -0800128
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400129 def test_key2param(self):
130 self.assertEqual('max_results', key2param('max-results'))
131 self.assertEqual('x007_bond', key2param('007-bond'))
132
Daniel Hermesc2113242013-02-27 10:16:13 -0800133 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
134 self.assertEqual(method_desc['httpMethod'], http_method)
135
136 method_desc_copy = copy.deepcopy(method_desc)
137 self.assertEqual(method_desc, method_desc_copy)
138
139 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
140
141 self.assertNotEqual(method_desc, method_desc_copy)
142
143 for param_name in STACK_QUERY_PARAMETERS:
144 self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
145 parameters[param_name])
146
INADA Naokid898a372015-03-04 03:52:46 +0900147 for param_name, value in six.iteritems(root_desc.get('parameters', {})):
Daniel Hermesc2113242013-02-27 10:16:13 -0800148 self.assertEqual(value, parameters[param_name])
149
150 return parameters
151
152 def test_fix_up_parameters_get(self):
153 parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
154 'GET', self.zoo_root_desc)
155 # Since http_method is 'GET'
INADA Naoki0bceb332014-08-20 15:27:52 +0900156 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800157
158 def test_fix_up_parameters_insert(self):
159 parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
160 'POST', self.zoo_root_desc)
161 body = {
162 'description': 'The request body.',
163 'type': 'object',
164 'required': True,
165 '$ref': 'Animal',
166 }
167 self.assertEqual(parameters['body'], body)
168
169 def test_fix_up_parameters_check_body(self):
170 dummy_root_desc = {}
171 no_payload_http_method = 'DELETE'
172 with_payload_http_method = 'PUT'
173
174 invalid_method_desc = {'response': 'Who cares'}
175 valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
176
177 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
178 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900179 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800180
181 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
182 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900183 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800184
185 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
186 with_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900187 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800188
189 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
190 with_payload_http_method)
191 body = {
192 'description': 'The request body.',
193 'type': 'object',
194 'required': True,
195 'key1': 'value1',
196 'key2': 'value2',
197 }
198 self.assertEqual(parameters['body'], body)
199
200 def _base_fix_up_method_description_test(
201 self, method_desc, initial_parameters, final_parameters,
202 final_accept, final_max_size, final_media_path_url):
203 fake_root_desc = {'rootUrl': 'http://root/',
204 'servicePath': 'fake/'}
205 fake_path_url = 'fake-path/'
206
207 accept, max_size, media_path_url = _fix_up_media_upload(
208 method_desc, fake_root_desc, fake_path_url, initial_parameters)
209 self.assertEqual(accept, final_accept)
210 self.assertEqual(max_size, final_max_size)
211 self.assertEqual(media_path_url, final_media_path_url)
212 self.assertEqual(initial_parameters, final_parameters)
213
214 def test_fix_up_media_upload_no_initial_invalid(self):
215 invalid_method_desc = {'response': 'Who cares'}
216 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
217 [], 0, None)
218
219 def test_fix_up_media_upload_no_initial_valid_minimal(self):
220 valid_method_desc = {'mediaUpload': {'accept': []}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700221 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
222 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800223 self._base_fix_up_method_description_test(
224 valid_method_desc, {}, final_parameters, [], 0,
225 'http://root/upload/fake/fake-path/')
226
227 def test_fix_up_media_upload_no_initial_valid_full(self):
228 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700229 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
230 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800231 ten_gb = 10 * 2**30
232 self._base_fix_up_method_description_test(
233 valid_method_desc, {}, final_parameters, ['*/*'],
234 ten_gb, 'http://root/upload/fake/fake-path/')
235
236 def test_fix_up_media_upload_with_initial_invalid(self):
237 invalid_method_desc = {'response': 'Who cares'}
238 initial_parameters = {'body': {}}
239 self._base_fix_up_method_description_test(
240 invalid_method_desc, initial_parameters,
241 initial_parameters, [], 0, None)
242
243 def test_fix_up_media_upload_with_initial_valid_minimal(self):
244 valid_method_desc = {'mediaUpload': {'accept': []}}
245 initial_parameters = {'body': {}}
246 final_parameters = {'body': {'required': False},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700247 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
248 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800249 self._base_fix_up_method_description_test(
250 valid_method_desc, initial_parameters, final_parameters, [], 0,
251 'http://root/upload/fake/fake-path/')
252
253 def test_fix_up_media_upload_with_initial_valid_full(self):
254 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
255 initial_parameters = {'body': {}}
256 final_parameters = {'body': {'required': False},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700257 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
258 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800259 ten_gb = 10 * 2**30
260 self._base_fix_up_method_description_test(
261 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
262 ten_gb, 'http://root/upload/fake/fake-path/')
263
264 def test_fix_up_method_description_get(self):
265 result = _fix_up_method_description(self.zoo_get_method_desc,
266 self.zoo_root_desc)
267 path_url = 'query'
268 http_method = 'GET'
269 method_id = 'bigquery.query'
270 accept = []
INADA Naoki0bceb332014-08-20 15:27:52 +0900271 max_size = 0
Daniel Hermesc2113242013-02-27 10:16:13 -0800272 media_path_url = None
273 self.assertEqual(result, (path_url, http_method, method_id, accept,
274 max_size, media_path_url))
275
276 def test_fix_up_method_description_insert(self):
277 result = _fix_up_method_description(self.zoo_insert_method_desc,
278 self.zoo_root_desc)
279 path_url = 'animals'
280 http_method = 'POST'
281 method_id = 'zoo.animals.insert'
282 accept = ['image/png']
INADA Naoki0bceb332014-08-20 15:27:52 +0900283 max_size = 1024
Daniel Hermesc2113242013-02-27 10:16:13 -0800284 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
285 self.assertEqual(result, (path_url, http_method, method_id, accept,
286 max_size, media_path_url))
287
Craig Citro7ee535d2015-02-23 10:11:14 -0800288 def test_urljoin(self):
289 # We want to exhaustively test various URL combinations.
290 simple_bases = ['https://www.googleapis.com', 'https://www.googleapis.com/']
291 long_urls = ['foo/v1/bar:custom?alt=json', '/foo/v1/bar:custom?alt=json']
292
293 long_bases = [
294 'https://www.googleapis.com/foo/v1',
295 'https://www.googleapis.com/foo/v1/',
296 ]
297 simple_urls = ['bar:custom?alt=json', '/bar:custom?alt=json']
298
299 final_url = 'https://www.googleapis.com/foo/v1/bar:custom?alt=json'
300 for base, url in itertools.product(simple_bases, long_urls):
301 self.assertEqual(final_url, _urljoin(base, url))
302 for base, url in itertools.product(long_bases, simple_urls):
303 self.assertEqual(final_url, _urljoin(base, url))
304
305
Daniel Hermes954e1242013-02-28 09:28:37 -0800306 def test_ResourceMethodParameters_zoo_get(self):
307 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
308
309 param_types = {'a': 'any',
310 'b': 'boolean',
311 'e': 'string',
312 'er': 'string',
313 'i': 'integer',
314 'n': 'number',
315 'o': 'object',
316 'q': 'string',
317 'rr': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900318 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800319 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
320 self.assertEqual(parameters.required_params, [])
321 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
322 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
323 self.assertEqual(sorted(parameters.query_params),
324 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
325 self.assertEqual(parameters.path_params, set())
326 self.assertEqual(parameters.param_types, param_types)
327 enum_params = {'e': ['foo', 'bar'],
328 'er': ['one', 'two', 'three']}
329 self.assertEqual(parameters.enum_params, enum_params)
330
331 def test_ResourceMethodParameters_zoo_animals_patch(self):
332 method_desc = self.zoo_animals_resource['methods']['patch']
333 parameters = ResourceMethodParameters(method_desc)
334
335 param_types = {'name': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900336 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800337 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
338 self.assertEqual(parameters.required_params, ['name'])
339 self.assertEqual(parameters.repeated_params, [])
340 self.assertEqual(parameters.pattern_params, {})
341 self.assertEqual(parameters.query_params, [])
342 self.assertEqual(parameters.path_params, set(['name']))
343 self.assertEqual(parameters.param_types, param_types)
344 self.assertEqual(parameters.enum_params, {})
345
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400346
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500347class DiscoveryErrors(unittest.TestCase):
348
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400349 def test_tests_should_be_run_with_strict_positional_enforcement(self):
350 try:
351 plus = build('plus', 'v1', None)
352 self.fail("should have raised a TypeError exception over missing http=.")
353 except TypeError:
354 pass
355
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500356 def test_failed_to_parse_discovery_json(self):
357 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
358 try:
Takashi Matsuo30125122015-08-19 11:42:32 -0700359 plus = build('plus', 'v1', http=self.http, cache_discovery=False)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500360 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500361 except InvalidJsonError:
362 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500363
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700364 def test_unknown_api_name_or_version(self):
365 http = HttpMockSequence([
366 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Ethan Bao12b7cd32016-03-14 14:25:10 -0700367 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700368 ])
369 with self.assertRaises(UnknownApiNameOrVersion):
370 plus = build('plus', 'v1', http=http, cache_discovery=False)
371
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800372 def test_credentials_and_http_mutually_exclusive(self):
373 http = HttpMock(datafile('plus.json'), {'status': '200'})
374 with self.assertRaises(ValueError):
375 build(
376 'plus', 'v1', http=http, credentials=mock.sentinel.credentials)
377
Jon Wayne Parrott860a6372017-03-06 10:13:50 -0800378 def test_credentials_and_developer_key_mutually_exclusive(self):
379 http = HttpMock(datafile('plus.json'), {'status': '200'})
380 with self.assertRaises(ValueError):
381 build(
382 'plus', 'v1', credentials=mock.sentinel.credentials,
383 developerKey=mock.sentinel.credentials)
384
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500385
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100386class DiscoveryFromDocument(unittest.TestCase):
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800387 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400388
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100389 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400390 discovery = open(datafile('plus.json')).read()
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800391 plus = build_from_document(
392 discovery, base="https://www.googleapis.com/",
393 credentials=self.MOCK_CREDENTIALS)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500394 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500395 self.assertTrue(hasattr(plus, 'activities'))
396
397 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400398 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700399 discovery = json.loads(discovery)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800400 plus = build_from_document(
401 discovery, base="https://www.googleapis.com/",
402 credentials=self.MOCK_CREDENTIALS)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500403 self.assertTrue(plus is not None)
404 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400405
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100406 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400407 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400408
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100409 base = "https://www.example.com/"
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800410 plus = build_from_document(
411 discovery, base=base, credentials=self.MOCK_CREDENTIALS)
Joe Gregorioa2838152012-07-16 11:52:17 -0400412 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100413
Igor Maravić22435292017-01-19 22:28:22 +0100414 def test_building_with_optional_http_with_authorization(self):
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700415 discovery = open(datafile('plus.json')).read()
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800416 plus = build_from_document(
417 discovery, base="https://www.googleapis.com/",
418 credentials=self.MOCK_CREDENTIALS)
Igor Maravić22435292017-01-19 22:28:22 +0100419
420 # plus service requires Authorization, hence we expect to see AuthorizedHttp object here
421 self.assertIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
422 self.assertIsInstance(plus._http.http, httplib2.Http)
423 self.assertIsInstance(plus._http.http.timeout, int)
424 self.assertGreater(plus._http.http.timeout, 0)
425
426 def test_building_with_optional_http_with_no_authorization(self):
427 discovery = open(datafile('plus.json')).read()
428 # Cleanup auth field, so we would use plain http client
429 discovery = json.loads(discovery)
430 discovery['auth'] = {}
431 discovery = json.dumps(discovery)
432
433 plus = build_from_document(
434 discovery, base="https://www.googleapis.com/",
435 credentials=self.MOCK_CREDENTIALS)
436 # plus service requires Authorization
437 self.assertIsInstance(plus._http, httplib2.Http)
438 self.assertIsInstance(plus._http.timeout, int)
439 self.assertGreater(plus._http.timeout, 0)
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700440
441 def test_building_with_explicit_http(self):
442 http = HttpMock()
443 discovery = open(datafile('plus.json')).read()
444 plus = build_from_document(
445 discovery, base="https://www.googleapis.com/", http=http)
446 self.assertEquals(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100447
Jon Wayne Parrott068eb352017-02-08 10:13:06 -0800448 def test_building_with_developer_key_skips_adc(self):
449 discovery = open(datafile('plus.json')).read()
450 plus = build_from_document(
451 discovery, base="https://www.googleapis.com/", developerKey='123')
452 self.assertIsInstance(plus._http, httplib2.Http)
453 # It should not be an AuthorizedHttp, because that would indicate that
454 # application default credentials were used.
455 self.assertNotIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
456
457
Joe Gregorioa98733f2011-09-16 10:12:28 -0400458class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400459 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700460 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400461
Joe Gregorio583d9e42011-09-16 15:54:15 -0400462 def tearDown(self):
463 os.environ = self.old_environ
464
465 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400466 # build() will raise an HttpError on a 400, use this to pick the request uri
467 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400468 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400469 try:
470 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400471 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400472 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400473 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400474 discoveryServiceUrl='http://example.com')
475 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900476 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400477 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400478
Joe Gregorio583d9e42011-09-16 15:54:15 -0400479 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400480 # build() will raise an HttpError on a 400, use this to pick the request uri
481 # out of the raised exception.
482 try:
483 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400484 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400485 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400486 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400487 discoveryServiceUrl='http://example.com')
488 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900489 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400490 self.assertEqual(e.uri, 'http://example.com')
491
Ethan Bao12b7cd32016-03-14 14:25:10 -0700492 def test_discovery_loading_from_v2_discovery_uri(self):
493 http = HttpMockSequence([
494 ({'status': '404'}, 'Not found'),
495 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
496 ])
497 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
498 self.assertTrue(hasattr(zoo, 'animals'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400499
Takashi Matsuo30125122015-08-19 11:42:32 -0700500class DiscoveryFromAppEngineCache(unittest.TestCase):
501 def test_appengine_memcache(self):
502 # Hack module import
503 self.orig_import = __import__
504 self.mocked_api = mock.MagicMock()
505
eesheeshc6425a02016-02-12 15:07:06 +0000506 def import_mock(name, *args, **kwargs):
Takashi Matsuo30125122015-08-19 11:42:32 -0700507 if name == 'google.appengine.api':
508 return self.mocked_api
eesheeshc6425a02016-02-12 15:07:06 +0000509 return self.orig_import(name, *args, **kwargs)
Takashi Matsuo30125122015-08-19 11:42:32 -0700510
511 import_fullname = '__builtin__.__import__'
512 if sys.version_info[0] >= 3:
513 import_fullname = 'builtins.__import__'
514
515 with mock.patch(import_fullname, side_effect=import_mock):
516 namespace = 'google-api-client'
517 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
518
519 self.mocked_api.memcache.get.return_value = None
520
521 plus = build('plus', 'v1', http=self.http)
522
523 # memcache.get is called once
524 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
525 self.mocked_api.memcache.get.assert_called_once_with(url,
526 namespace=namespace)
527
528 # memcache.set is called once
529 with open(datafile('plus.json')) as f:
530 content = f.read()
531 self.mocked_api.memcache.set.assert_called_once_with(
532 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace)
533
534 # Returns the cached content this time.
535 self.mocked_api.memcache.get.return_value = content
536
537 # Make sure the contents are returned from the cache.
538 # (Otherwise it should through an error)
539 self.http = HttpMock(None, {'status': '200'})
540
541 plus = build('plus', 'v1', http=self.http)
542
543 # memcache.get is called twice
544 self.mocked_api.memcache.get.assert_has_calls(
545 [mock.call(url, namespace=namespace),
546 mock.call(url, namespace=namespace)])
547
548 # memcahce.set is called just once
549 self.mocked_api.memcache.set.assert_called_once_with(
550 url, content, time=DISCOVERY_DOC_MAX_AGE,namespace=namespace)
551
552
553class DictCache(Cache):
554 def __init__(self):
555 self.d = {}
556 def get(self, url):
557 return self.d.get(url, None)
558 def set(self, url, content):
559 self.d[url] = content
560 def contains(self, url):
561 return url in self.d
562
563
564class DiscoveryFromFileCache(unittest.TestCase):
565 def test_file_based_cache(self):
566 cache = mock.Mock(wraps=DictCache())
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -0700567 with mock.patch('googleapiclient.discovery_cache.autodetect',
568 return_value=cache):
Takashi Matsuo30125122015-08-19 11:42:32 -0700569 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
570
571 plus = build('plus', 'v1', http=self.http)
572
573 # cache.get is called once
574 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
575 cache.get.assert_called_once_with(url)
576
577 # cache.set is called once
578 with open(datafile('plus.json')) as f:
579 content = f.read()
580 cache.set.assert_called_once_with(url, content)
581
582 # Make sure there is a cache entry for the plus v1 discovery doc.
583 self.assertTrue(cache.contains(url))
584
585 # Make sure the contents are returned from the cache.
586 # (Otherwise it should through an error)
587 self.http = HttpMock(None, {'status': '200'})
588
589 plus = build('plus', 'v1', http=self.http)
590
591 # cache.get is called twice
592 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
593
594 # cahce.set is called just once
595 cache.set.assert_called_once_with(url, content)
596
597
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400598class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400599
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400600 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500601 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400602 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400603
604 # Missing required parameters
605 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500606 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400607 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900608 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400609 self.assertTrue('Missing' in str(e))
610
Joe Gregorio2467afa2012-06-20 12:21:25 -0400611 # Missing required parameters even if supplied as None.
612 try:
613 plus.activities().list(collection=None, userId=None)
614 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900615 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400616 self.assertTrue('Missing' in str(e))
617
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400618 # Parameter doesn't match regex
619 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500620 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400621 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900622 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500623 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400624
625 # Unexpected parameter
626 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500627 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400628 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900629 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400630 self.assertTrue('unexpected' in str(e))
631
Joe Gregoriobee86832011-02-22 10:00:19 -0500632 def _check_query_types(self, request):
Pat Ferated5b61bd2015-03-03 16:04:11 -0800633 parsed = urlparse(request.uri)
Joe Gregoriobee86832011-02-22 10:00:19 -0500634 q = parse_qs(parsed[4])
635 self.assertEqual(q['q'], ['foo'])
636 self.assertEqual(q['i'], ['1'])
637 self.assertEqual(q['n'], ['1.0'])
638 self.assertEqual(q['b'], ['false'])
639 self.assertEqual(q['a'], ['[1, 2, 3]'])
640 self.assertEqual(q['o'], ['{\'a\': 1}'])
641 self.assertEqual(q['e'], ['bar'])
642
643 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400644 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400645 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500646
Joe Gregorioa98733f2011-09-16 10:12:28 -0400647 request = zoo.query(
648 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 -0500649 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400650 request = zoo.query(
651 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500652 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500653
Joe Gregorioa98733f2011-09-16 10:12:28 -0400654 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800655 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 -0500656
657 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800658 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
659 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500660 self._check_query_types(request)
661
Craig Citro1e742822012-03-01 12:59:22 -0800662 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500663 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800664
Joe Gregorio13217952011-02-22 15:37:38 -0500665 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400666 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400667 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400668 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500669
Pat Ferated5b61bd2015-03-03 16:04:11 -0800670 parsed = urlparse(request.uri)
Joe Gregorioca876e42011-02-22 19:39:42 -0500671 q = parse_qs(parsed[4])
672 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400673 self.assertEqual(q['fields'], ['description'])
674
Joe Gregorio2467afa2012-06-20 12:21:25 -0400675 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400676 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400677 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400678 request = zoo.query(trace=None, fields='description')
679
Pat Ferated5b61bd2015-03-03 16:04:11 -0800680 parsed = urlparse(request.uri)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400681 q = parse_qs(parsed[4])
682 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400683
Joe Gregorioe08a1662011-12-07 09:48:22 -0500684 def test_model_added_query_parameters(self):
685 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400686 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500687 request = zoo.animals().get(name='Lion')
688
Pat Ferated5b61bd2015-03-03 16:04:11 -0800689 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500690 q = parse_qs(parsed[4])
691 self.assertEqual(q['alt'], ['json'])
692 self.assertEqual(request.headers['accept'], 'application/json')
693
694 def test_fallback_to_raw_model(self):
695 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400696 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500697 request = zoo.animals().getmedia(name='Lion')
698
Pat Ferated5b61bd2015-03-03 16:04:11 -0800699 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500700 q = parse_qs(parsed[4])
701 self.assertTrue('alt' not in q)
702 self.assertEqual(request.headers['accept'], '*/*')
703
Joe Gregoriof4153422011-03-18 22:45:18 -0400704 def test_patch(self):
705 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400706 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400707 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
708
709 self.assertEqual(request.method, 'PATCH')
710
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -0400711 def test_batch_request_from_discovery(self):
712 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
713 # zoo defines a batchPath
714 zoo = build('zoo', 'v1', http=self.http)
715 batch_request = zoo.new_batch_http_request()
716 self.assertEqual(batch_request._batch_uri,
717 "https://www.googleapis.com/batchZoo")
718
719 def test_batch_request_from_default(self):
720 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
721 # plus does not define a batchPath
722 plus = build('plus', 'v1', http=self.http)
723 batch_request = plus.new_batch_http_request()
724 self.assertEqual(batch_request._batch_uri,
725 "https://www.googleapis.com/batch")
726
Joe Gregoriof4153422011-03-18 22:45:18 -0400727 def test_tunnel_patch(self):
728 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400729 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400730 ({'status': '200'}, 'echo_request_headers_as_json'),
731 ])
732 http = tunnel_patch(http)
Takashi Matsuo30125122015-08-19 11:42:32 -0700733 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400734 resp = zoo.animals().patch(
735 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400736
737 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500738
Joe Gregorio7b70f432011-11-09 10:18:51 -0500739 def test_plus_resources(self):
740 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400741 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500742 self.assertTrue(getattr(plus, 'activities'))
743 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400744
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800745 def test_oauth2client_credentials(self):
746 credentials = mock.Mock(spec=GoogleCredentials)
747 credentials.create_scoped_required.return_value = False
Orest Bolohane92c9002014-05-30 11:15:43 -0700748
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800749 discovery = open(datafile('plus.json')).read()
750 service = build_from_document(discovery, credentials=credentials)
751 self.assertEqual(service._http, credentials.authorize.return_value)
Orest Bolohane92c9002014-05-30 11:15:43 -0700752
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800753 def test_google_auth_credentials(self):
754 credentials = mock.Mock(spec=google.auth.credentials.Credentials)
755 discovery = open(datafile('plus.json')).read()
756 service = build_from_document(discovery, credentials=credentials)
757
758 self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp)
759 self.assertEqual(service._http.credentials, credentials)
760
761 def test_no_scopes_no_credentials(self):
762 # Zoo doesn't have scopes
763 discovery = open(datafile('zoo.json')).read()
764 service = build_from_document(discovery)
765 # Should be an ordinary httplib2.Http instance and not AuthorizedHttp.
766 self.assertIsInstance(service._http, httplib2.Http)
Orest Bolohane92c9002014-05-30 11:15:43 -0700767
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400768 def test_full_featured(self):
769 # Zoo should exercise all discovery facets
770 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500771 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400772 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400773 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500774
Joe Gregoriof4153422011-03-18 22:45:18 -0400775 request = zoo.animals().list(name='bat', projection="full")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800776 parsed = urlparse(request.uri)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400777 q = parse_qs(parsed[4])
778 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400779 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400780
Joe Gregorio3fada332011-01-07 17:07:45 -0500781 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500782 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400783 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500784 self.assertTrue(getattr(zoo, 'animals'))
785 request = zoo.my().favorites().list(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800786 parsed = urlparse(request.uri)
Joe Gregorio3fada332011-01-07 17:07:45 -0500787 q = parse_qs(parsed[4])
788 self.assertEqual(q['max-results'], ['5'])
789
Pat Feratec6050872015-03-03 18:24:59 -0800790 @unittest.skipIf(six.PY3, 'print is not a reserved name in Python 3')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400791 def test_methods_with_reserved_names(self):
792 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400793 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400794 self.assertTrue(getattr(zoo, 'animals'))
795 request = zoo.global_().print_().assert_(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800796 parsed = urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400797 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400798
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500799 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500800 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400801 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500802 self.assertTrue(getattr(zoo, 'query'))
803 request = zoo.query(q="foo")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800804 parsed = urlparse(request.uri)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500805 q = parse_qs(parsed[4])
806 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400807
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400808 def test_simple_media_uploads(self):
809 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400810 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400811 doc = getattr(zoo.animals().insert, '__doc__')
812 self.assertTrue('media_body' in doc)
813
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400814 def test_simple_media_upload_no_max_size_provided(self):
815 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400816 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400817 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
818 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800819 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400820
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400821 def test_simple_media_raise_correct_exceptions(self):
822 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400823 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400824
825 try:
826 zoo.animals().insert(media_body=datafile('smiley.png'))
827 self.fail("should throw exception if media is too large.")
828 except MediaUploadSizeError:
829 pass
830
831 try:
832 zoo.animals().insert(media_body=datafile('small.jpg'))
833 self.fail("should throw exception if mimetype is unacceptable.")
834 except UnacceptableMimeTypeError:
835 pass
836
837 def test_simple_media_good_upload(self):
838 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400839 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400840
841 request = zoo.animals().insert(media_body=datafile('small.png'))
842 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800843 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500844 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400845 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500846 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400847
Brian J. Watson38051ac2016-10-25 07:53:08 -0700848 def test_simple_media_unknown_mimetype(self):
849 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
850 zoo = build('zoo', 'v1', http=self.http)
851
852 try:
853 zoo.animals().insert(media_body=datafile('small-png'))
854 self.fail("should throw exception if mimetype is unknown.")
855 except UnknownFileType:
856 pass
857
858 request = zoo.animals().insert(media_body=datafile('small-png'),
859 media_mime_type='image/png')
860 self.assertEquals('image/png', request.headers['content-type'])
861 self.assertEquals(b'PNG', request.body[1:4])
862 assertUrisEqual(self,
863 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
864 request.uri)
865
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400866 def test_multipart_media_raise_correct_exceptions(self):
867 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400868 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400869
870 try:
871 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
872 self.fail("should throw exception if media is too large.")
873 except MediaUploadSizeError:
874 pass
875
876 try:
877 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
878 self.fail("should throw exception if mimetype is unacceptable.")
879 except UnacceptableMimeTypeError:
880 pass
881
882 def test_multipart_media_good_upload(self):
883 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400884 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400885
886 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400887 self.assertTrue(request.headers['content-type'].startswith(
888 'multipart/related'))
Phil Ruffwind26178fc2015-10-13 19:00:33 -0400889 with open(datafile('small.png'), 'rb') as f:
890 contents = f.read()
891 boundary = re.match(b'--=+([^=]+)', request.body).group(1)
892 self.assertEqual(
893 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
894 b'--===============' + boundary + b'==\n' +
895 b'Content-Type: application/json\n' +
896 b'MIME-Version: 1.0\n\n' +
897 b'{"data": {}}\n' +
898 b'--===============' + boundary + b'==\n' +
899 b'Content-Type: image/png\n' +
900 b'MIME-Version: 1.0\n' +
901 b'Content-Transfer-Encoding: binary\n\n' +
902 contents +
903 b'\n--===============' + boundary + b'==--')
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500904 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400905 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500906 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400907
908 def test_media_capable_method_without_media(self):
909 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400910 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400911
912 request = zoo.animals().insert(body={})
913 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400914
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500915 def test_resumable_multipart_media_good_upload(self):
916 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400917 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500918
919 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
920 request = zoo.animals().insert(media_body=media_upload, body={})
921 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500922 'application/json'))
923 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500924 self.assertEquals(media_upload, request.resumable)
925
926 self.assertEquals('image/png', request.resumable.mimetype())
927
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500928 self.assertNotEquals(request.body, None)
929 self.assertEquals(request.resumable_uri, None)
930
931 http = HttpMockSequence([
932 ({'status': '200',
933 'location': 'http://upload.example.com'}, ''),
934 ({'status': '308',
Matt Carroll94a53942016-12-20 13:56:43 -0800935 'location': 'http://upload.example.com/2'}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500936 ({'status': '308',
937 'location': 'http://upload.example.com/3',
Matt Carroll94a53942016-12-20 13:56:43 -0800938 'range': '0-12'}, ''),
939 ({'status': '308',
940 'location': 'http://upload.example.com/4',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500941 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500942 ({'status': '200'}, '{"foo": "bar"}'),
943 ])
944
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400945 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500946 self.assertEquals(None, body)
947 self.assertTrue(isinstance(status, MediaUploadProgress))
Matt Carroll94a53942016-12-20 13:56:43 -0800948 self.assertEquals(0, status.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500949
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500950 # Two requests should have been made and the resumable_uri should have been
951 # updated for each one.
952 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
Matt Carroll94a53942016-12-20 13:56:43 -0800953 self.assertEquals(media_upload, request.resumable)
954 self.assertEquals(0, request.resumable_progress)
955
956 # This next chuck call should upload the first chunk
957 status, body = request.next_chunk(http=http)
958 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500959 self.assertEquals(media_upload, request.resumable)
960 self.assertEquals(13, request.resumable_progress)
961
Matt Carroll94a53942016-12-20 13:56:43 -0800962 # This call will upload the next chunk
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400963 status, body = request.next_chunk(http=http)
Matt Carroll94a53942016-12-20 13:56:43 -0800964 self.assertEquals(request.resumable_uri, 'http://upload.example.com/4')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500965 self.assertEquals(media_upload.size()-1, request.resumable_progress)
966 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500967
968 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400969 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500970 self.assertEquals(body, {"foo": "bar"})
971 self.assertEquals(status, None)
972
973
974 def test_resumable_media_good_upload(self):
975 """Not a multipart upload."""
976 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400977 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500978
979 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
980 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500981 self.assertEquals(media_upload, request.resumable)
982
983 self.assertEquals('image/png', request.resumable.mimetype())
984
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500985 self.assertEquals(request.body, None)
986 self.assertEquals(request.resumable_uri, None)
987
988 http = HttpMockSequence([
989 ({'status': '200',
990 'location': 'http://upload.example.com'}, ''),
991 ({'status': '308',
992 'location': 'http://upload.example.com/2',
993 'range': '0-12'}, ''),
994 ({'status': '308',
995 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500996 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500997 ({'status': '200'}, '{"foo": "bar"}'),
998 ])
999
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001000 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001001 self.assertEquals(None, body)
1002 self.assertTrue(isinstance(status, MediaUploadProgress))
1003 self.assertEquals(13, status.resumable_progress)
1004
1005 # Two requests should have been made and the resumable_uri should have been
1006 # updated for each one.
1007 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
1008
1009 self.assertEquals(media_upload, request.resumable)
1010 self.assertEquals(13, request.resumable_progress)
1011
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001012 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001013 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -05001014 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001015 self.assertEquals(request.body, None)
1016
1017 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001018 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001019 self.assertEquals(body, {"foo": "bar"})
1020 self.assertEquals(status, None)
1021
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001022 def test_resumable_media_good_upload_from_execute(self):
1023 """Not a multipart upload."""
1024 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001025 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001026
1027 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1028 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -05001029 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -04001030 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -05001031 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001032
1033 http = HttpMockSequence([
1034 ({'status': '200',
1035 'location': 'http://upload.example.com'}, ''),
1036 ({'status': '308',
1037 'location': 'http://upload.example.com/2',
1038 'range': '0-12'}, ''),
1039 ({'status': '308',
1040 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -05001041 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001042 ({'status': '200'}, '{"foo": "bar"}'),
1043 ])
1044
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001045 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001046 self.assertEquals(body, {"foo": "bar"})
1047
1048 def test_resumable_media_fail_unknown_response_code_first_request(self):
1049 """Not a multipart upload."""
1050 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001051 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001052
1053 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1054 request = zoo.animals().insert(media_body=media_upload, body=None)
1055
1056 http = HttpMockSequence([
1057 ({'status': '400',
1058 'location': 'http://upload.example.com'}, ''),
1059 ])
1060
Joe Gregoriobaf04802013-03-01 12:27:06 -05001061 try:
1062 request.execute(http=http)
1063 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +09001064 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -05001065 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001066
1067 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
1068 """Not a multipart upload."""
1069 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001070 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001071
1072 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1073 request = zoo.animals().insert(media_body=media_upload, body=None)
1074
1075 http = HttpMockSequence([
1076 ({'status': '200',
1077 'location': 'http://upload.example.com'}, ''),
1078 ({'status': '400'}, ''),
1079 ])
1080
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001081 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001082 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001083
Joe Gregorio910b9b12012-06-12 09:36:30 -04001084 http = HttpMockSequence([
1085 ({'status': '308',
1086 'range': '0-5'}, ''),
1087 ({'status': '308',
1088 'range': '0-6'}, ''),
1089 ])
1090
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001091 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001092 self.assertEquals(status.resumable_progress, 7,
1093 'Should have first checked length and then tried to PUT more.')
1094 self.assertFalse(request._in_error_state)
1095
1096 # Put it back in an error state.
1097 http = HttpMockSequence([
1098 ({'status': '400'}, ''),
1099 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001100 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001101 self.assertTrue(request._in_error_state)
1102
1103 # Pretend the last request that 400'd actually succeeded.
1104 http = HttpMockSequence([
1105 ({'status': '200'}, '{"foo": "bar"}'),
1106 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001107 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001108 self.assertEqual(body, {'foo': 'bar'})
1109
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001110 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1111 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1112 zoo = build('zoo', 'v1', http=self.http)
1113
Pat Ferateed9affd2015-03-03 16:03:15 -08001114 # Set up a seekable stream and try to upload in single chunk.
Pat Ferate2b140222015-03-03 18:05:11 -08001115 fd = BytesIO(b'01234"56789"')
Pat Ferateed9affd2015-03-03 16:03:15 -08001116 media_upload = MediaIoBaseUpload(
1117 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001118
Pat Ferateed9affd2015-03-03 16:03:15 -08001119 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001120
Pat Ferateed9affd2015-03-03 16:03:15 -08001121 # The single chunk fails, restart at the right point.
1122 http = HttpMockSequence([
1123 ({'status': '200',
1124 'location': 'http://upload.example.com'}, ''),
1125 ({'status': '308',
1126 'location': 'http://upload.example.com/2',
1127 'range': '0-4'}, ''),
1128 ({'status': '200'}, 'echo_request_body'),
1129 ])
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001130
Pat Ferateed9affd2015-03-03 16:03:15 -08001131 body = request.execute(http=http)
1132 self.assertEqual('56789', body)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001133
1134 def test_media_io_base_stream_chunksize_resume(self):
1135 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1136 zoo = build('zoo', 'v1', http=self.http)
1137
Pat Ferateed9affd2015-03-03 16:03:15 -08001138 # Set up a seekable stream and try to upload in chunks.
Pat Ferate2b140222015-03-03 18:05:11 -08001139 fd = BytesIO(b'0123456789')
Pat Ferateed9affd2015-03-03 16:03:15 -08001140 media_upload = MediaIoBaseUpload(
1141 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
1142
1143 request = zoo.animals().insert(media_body=media_upload, body=None)
1144
1145 # The single chunk fails, pull the content sent out of the exception.
1146 http = HttpMockSequence([
1147 ({'status': '200',
1148 'location': 'http://upload.example.com'}, ''),
1149 ({'status': '400'}, 'echo_request_body'),
1150 ])
1151
Joe Gregorio5c120db2012-08-23 09:13:55 -04001152 try:
Pat Ferateed9affd2015-03-03 16:03:15 -08001153 body = request.execute(http=http)
1154 except HttpError as e:
Pat Ferate2b140222015-03-03 18:05:11 -08001155 self.assertEqual(b'01234', e.content)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001156
Joe Gregorio910b9b12012-06-12 09:36:30 -04001157 def test_resumable_media_handle_uploads_of_unknown_size(self):
1158 http = HttpMockSequence([
1159 ({'status': '200',
1160 'location': 'http://upload.example.com'}, ''),
1161 ({'status': '200'}, 'echo_request_headers_as_json'),
1162 ])
1163
1164 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001165 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001166
Joe Gregorio910b9b12012-06-12 09:36:30 -04001167 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001168 class IoBaseUnknownLength(MediaUpload):
1169 def chunksize(self):
1170 return 10
1171
1172 def mimetype(self):
1173 return 'image/png'
1174
1175 def size(self):
1176 return None
1177
1178 def resumable(self):
1179 return True
1180
1181 def getbytes(self, begin, length):
1182 return '0123456789'
1183
1184 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -04001185
1186 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001187 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001188 self.assertEqual(body, {
1189 'Content-Range': 'bytes 0-9/*',
1190 'Content-Length': '10',
1191 })
Joe Gregorio44454e42012-06-15 08:38:53 -04001192
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001193 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1194 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1195 zoo = build('zoo', 'v1', http=self.http)
1196
1197 class IoBaseHasStream(MediaUpload):
1198 def chunksize(self):
1199 return 10
1200
1201 def mimetype(self):
1202 return 'image/png'
1203
1204 def size(self):
1205 return None
1206
1207 def resumable(self):
1208 return True
1209
1210 def getbytes(self, begin, length):
1211 return '0123456789'
1212
1213 def has_stream(self):
1214 return True
1215
1216 def stream(self):
1217 raise NotImplementedError()
1218
1219 upload = IoBaseHasStream()
1220
1221 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001222
1223 sys.version_info = (2, 6, 5, 'final', 0)
1224
1225 request = zoo.animals().insert(media_body=upload, body=None)
1226
1227 # This should raise an exception because stream() will be called.
1228 http = HttpMockSequence([
1229 ({'status': '200',
1230 'location': 'http://upload.example.com'}, ''),
1231 ({'status': '200'}, 'echo_request_headers_as_json'),
1232 ])
1233
1234 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
1235
1236 sys.version_info = orig_version
1237
Joe Gregorio44454e42012-06-15 08:38:53 -04001238 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1239 http = HttpMockSequence([
1240 ({'status': '200',
1241 'location': 'http://upload.example.com'}, ''),
1242 ({'status': '200'}, 'echo_request_headers_as_json'),
1243 ])
1244
1245 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001246 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001247
Pat Ferate2b140222015-03-03 18:05:11 -08001248 fd = BytesIO(b'data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001249
1250 # Create an upload that doesn't know the full size of the media.
1251 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001252 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001253
1254 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001255 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001256 self.assertEqual(body, {
1257 'Content-Range': 'bytes 0-13/14',
1258 'Content-Length': '14',
1259 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001260
1261 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1262 http = HttpMockSequence([
1263 ({'status': '200',
1264 'location': 'http://upload.example.com'}, ''),
1265 ({'status': '400'}, ''),
1266 ])
1267
1268 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001269 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001270
1271 # Create an upload that doesn't know the full size of the media.
Pat Ferate2b140222015-03-03 18:05:11 -08001272 fd = BytesIO(b'data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001273
1274 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001275 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001276
1277 request = zoo.animals().insert(media_body=upload, body=None)
1278
1279 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001280 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001281
1282 http = HttpMockSequence([
1283 ({'status': '400',
1284 'range': '0-5'}, 'echo_request_headers_as_json'),
1285 ])
1286 try:
1287 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001288 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001289 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001290 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001291 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001292 'content-length': '0'
1293 }
INADA Naoki09157612015-03-25 01:51:03 +09001294 self.assertEqual(expected, json.loads(e.content.decode('utf-8')),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001295 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001296
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001297 def test_pickle(self):
1298 sorted_resource_keys = ['_baseUrl',
1299 '_developerKey',
1300 '_dynamic_attrs',
1301 '_http',
1302 '_model',
1303 '_requestBuilder',
1304 '_resourceDesc',
1305 '_rootDesc',
1306 '_schema',
1307 'animals',
1308 'global_',
1309 'load',
1310 'loadNoTemplate',
1311 'my',
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001312 'new_batch_http_request',
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001313 'query',
1314 'scopedAnimals']
1315
1316 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1317 zoo = build('zoo', 'v1', http=http)
1318 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1319
1320 pickled_zoo = pickle.dumps(zoo)
1321 new_zoo = pickle.loads(pickled_zoo)
1322 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1323 self.assertTrue(hasattr(new_zoo, 'animals'))
1324 self.assertTrue(callable(new_zoo.animals))
1325 self.assertTrue(hasattr(new_zoo, 'global_'))
1326 self.assertTrue(callable(new_zoo.global_))
1327 self.assertTrue(hasattr(new_zoo, 'load'))
1328 self.assertTrue(callable(new_zoo.load))
1329 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1330 self.assertTrue(callable(new_zoo.loadNoTemplate))
1331 self.assertTrue(hasattr(new_zoo, 'my'))
1332 self.assertTrue(callable(new_zoo.my))
1333 self.assertTrue(hasattr(new_zoo, 'query'))
1334 self.assertTrue(callable(new_zoo.query))
1335 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1336 self.assertTrue(callable(new_zoo.scopedAnimals))
1337
Joe Gregorio003b6e42013-02-13 15:42:19 -05001338 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001339 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1340 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1341 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1342 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1343 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1344 # _http, _model and _schema won't be equal since we will get new
1345 # instances upon un-pickling
1346
1347 def _dummy_zoo_request(self):
1348 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1349 zoo_contents = fh.read()
1350
1351 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1352 {'api': 'zoo', 'apiVersion': 'v1'})
1353 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001354 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1355 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001356
Igor Maravić22435292017-01-19 22:28:22 +01001357 http = build_http()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001358 original_request = http.request
1359 def wrapped_request(uri, method='GET', *args, **kwargs):
1360 if uri == zoo_uri:
1361 return httplib2.Response({'status': '200'}), zoo_contents
1362 return original_request(uri, method=method, *args, **kwargs)
1363 http.request = wrapped_request
1364 return http
1365
1366 def _dummy_token(self):
1367 access_token = 'foo'
1368 client_id = 'some_client_id'
1369 client_secret = 'cOuDdkfjxxnv+'
1370 refresh_token = '1/0/a.df219fjls0'
1371 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001372 user_agent = 'refresh_checker/1.0'
1373 return OAuth2Credentials(
1374 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001375 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001376 user_agent)
1377
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001378 def test_pickle_with_credentials(self):
1379 credentials = self._dummy_token()
1380 http = self._dummy_zoo_request()
1381 http = credentials.authorize(http)
1382 self.assertTrue(hasattr(http.request, 'credentials'))
1383
1384 zoo = build('zoo', 'v1', http=http)
1385 pickled_zoo = pickle.dumps(zoo)
1386 new_zoo = pickle.loads(pickled_zoo)
1387 self.assertEqual(sorted(zoo.__dict__.keys()),
1388 sorted(new_zoo.__dict__.keys()))
1389 new_http = new_zoo._http
1390 self.assertFalse(hasattr(new_http.request, 'credentials'))
1391
andrewnestera4a44cf2017-03-31 16:09:31 +03001392 def test_resumable_media_upload_no_content(self):
1393 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1394 zoo = build('zoo', 'v1', http=self.http)
1395
1396 media_upload = MediaFileUpload(datafile('empty'), resumable=True)
1397 request = zoo.animals().insert(media_body=media_upload, body=None)
1398
1399 self.assertEquals(media_upload, request.resumable)
1400 self.assertEquals(request.body, None)
1401 self.assertEquals(request.resumable_uri, None)
1402
1403 http = HttpMockSequence([
1404 ({'status': '200',
1405 'location': 'http://upload.example.com'}, ''),
1406 ({'status': '308',
1407 'location': 'http://upload.example.com/2',
1408 'range': '0-0'}, ''),
1409 ])
1410
1411 status, body = request.next_chunk(http=http)
1412 self.assertEquals(None, body)
1413 self.assertTrue(isinstance(status, MediaUploadProgress))
1414 self.assertEquals(0, status.progress())
1415
Joe Gregorio708388c2012-06-15 13:43:04 -04001416
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001417class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001418
Joe Gregorio3c676f92011-07-25 10:38:14 -04001419 def test_next_successful_none_on_no_next_page_token(self):
1420 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001421 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001422 request = tasks.tasklists().list()
1423 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1424
Son Dinh2a9a2132015-07-23 16:30:56 +00001425 def test_next_successful_none_on_empty_page_token(self):
1426 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
1427 tasks = build('tasks', 'v1', http=self.http)
1428 request = tasks.tasklists().list()
1429 next_request = tasks.tasklists().list_next(
1430 request, {'nextPageToken': ''})
1431 self.assertEqual(None, next_request)
1432
Joe Gregorio3c676f92011-07-25 10:38:14 -04001433 def test_next_successful_with_next_page_token(self):
1434 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001435 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001436 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001437 next_request = tasks.tasklists().list_next(
1438 request, {'nextPageToken': '123abc'})
Pat Ferated5b61bd2015-03-03 16:04:11 -08001439 parsed = list(urlparse(next_request.uri))
Joe Gregorio3c676f92011-07-25 10:38:14 -04001440 q = parse_qs(parsed[4])
1441 self.assertEqual(q['pageToken'][0], '123abc')
1442
Thomas Coffee20af04d2017-02-10 15:24:44 -08001443 def test_next_successful_with_next_page_token_alternate_name(self):
1444 self.http = HttpMock(datafile('bigquery.json'), {'status': '200'})
1445 bigquery = build('bigquery', 'v2', http=self.http)
1446 request = bigquery.tabledata().list(datasetId='', projectId='', tableId='')
1447 next_request = bigquery.tabledata().list_next(
1448 request, {'pageToken': '123abc'})
1449 parsed = list(urlparse(next_request.uri))
1450 q = parse_qs(parsed[4])
1451 self.assertEqual(q['pageToken'][0], '123abc')
1452
1453 def test_next_successful_with_next_page_token_in_body(self):
1454 self.http = HttpMock(datafile('logging.json'), {'status': '200'})
1455 logging = build('logging', 'v2', http=self.http)
1456 request = logging.entries().list(body={})
1457 next_request = logging.entries().list_next(
1458 request, {'nextPageToken': '123abc'})
1459 body = JsonModel().deserialize(next_request.body)
1460 self.assertEqual(body['pageToken'], '123abc')
1461
Joe Gregorio555f33c2011-08-19 14:56:07 -04001462 def test_next_with_method_with_no_properties(self):
1463 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001464 service = build('latitude', 'v1', http=self.http)
Thomas Coffee20af04d2017-02-10 15:24:44 -08001465 service.currentLocation().get()
1466
1467 def test_next_nonexistent_with_no_next_page_token(self):
1468 self.http = HttpMock(datafile('drive.json'), {'status': '200'})
1469 drive = build('drive', 'v3', http=self.http)
1470 drive.changes().watch(body={})
1471 self.assertFalse(callable(getattr(drive.changes(), 'watch_next', None)))
1472
1473 def test_next_successful_with_next_page_token_required(self):
1474 self.http = HttpMock(datafile('drive.json'), {'status': '200'})
1475 drive = build('drive', 'v3', http=self.http)
1476 request = drive.changes().list(pageToken='startPageToken')
1477 next_request = drive.changes().list_next(
1478 request, {'nextPageToken': '123abc'})
1479 parsed = list(urlparse(next_request.uri))
1480 q = parse_qs(parsed[4])
1481 self.assertEqual(q['pageToken'][0], '123abc')
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001482
Joe Gregorioa98733f2011-09-16 10:12:28 -04001483
Joe Gregorio708388c2012-06-15 13:43:04 -04001484class MediaGet(unittest.TestCase):
1485
1486 def test_get_media(self):
1487 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001488 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001489 request = zoo.animals().get_media(name='Lion')
1490
Pat Ferated5b61bd2015-03-03 16:04:11 -08001491 parsed = urlparse(request.uri)
Joe Gregorio708388c2012-06-15 13:43:04 -04001492 q = parse_qs(parsed[4])
1493 self.assertEqual(q['alt'], ['media'])
1494 self.assertEqual(request.headers['accept'], '*/*')
1495
1496 http = HttpMockSequence([
1497 ({'status': '200'}, 'standing in for media'),
1498 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001499 response = request.execute(http=http)
INADA Naoki09157612015-03-25 01:51:03 +09001500 self.assertEqual(b'standing in for media', response)
Joe Gregorio708388c2012-06-15 13:43:04 -04001501
1502
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001503if __name__ == '__main__':
1504 unittest.main()