blob: 43ab2c79a8d9c8783a5eadeb6c9aafbbdafd158a [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
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070026__author__ = "jcgregorio@google.com (Joe Gregorio)"
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040027
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
Pavel Kiselev21af37b2020-06-18 19:50:03 +030041from collections import defaultdict
Joe Gregoriodc106fc2012-11-20 14:30:14 -050042
arithmetic1728981eadf2020-06-02 10:20:10 -070043from parameterized import parameterized
Takashi Matsuo30125122015-08-19 11:42:32 -070044import mock
45
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080046import google.auth.credentials
arithmetic1728981eadf2020-06-02 10:20:10 -070047from google.auth.transport import mtls
48from google.auth.exceptions import MutualTLSChannelError
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080049import google_auth_httplib2
Bu Sun Kim790e7022020-09-11 20:18:06 -060050import google.api_core.exceptions
51
John Asmuth864311d2014-04-24 15:46:08 -040052from googleapiclient.discovery import _fix_up_media_upload
53from googleapiclient.discovery import _fix_up_method_description
54from googleapiclient.discovery import _fix_up_parameters
Craig Citro7ee535d2015-02-23 10:11:14 -080055from googleapiclient.discovery import _urljoin
John Asmuth864311d2014-04-24 15:46:08 -040056from googleapiclient.discovery import build
57from googleapiclient.discovery import build_from_document
58from googleapiclient.discovery import DISCOVERY_URI
59from googleapiclient.discovery import key2param
60from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
Brian J. Watson38051ac2016-10-25 07:53:08 -070061from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE
John Asmuth864311d2014-04-24 15:46:08 -040062from googleapiclient.discovery import ResourceMethodParameters
63from googleapiclient.discovery import STACK_QUERY_PARAMETERS
64from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Dmitry Frenkelf3348f92020-07-15 13:05:58 -070065from googleapiclient.discovery import V1_DISCOVERY_URI
66from googleapiclient.discovery import V2_DISCOVERY_URI
Takashi Matsuo30125122015-08-19 11:42:32 -070067from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE
68from googleapiclient.discovery_cache.base import Cache
John Asmuth864311d2014-04-24 15:46:08 -040069from googleapiclient.errors import HttpError
70from googleapiclient.errors import InvalidJsonError
71from googleapiclient.errors import MediaUploadSizeError
72from googleapiclient.errors import ResumableUploadError
73from googleapiclient.errors import UnacceptableMimeTypeError
Takashi Matsuo3772f9d2015-09-04 12:25:55 -070074from googleapiclient.errors import UnknownApiNameOrVersion
Brian J. Watson38051ac2016-10-25 07:53:08 -070075from googleapiclient.errors import UnknownFileType
Igor Maravić22435292017-01-19 22:28:22 +010076from googleapiclient.http import build_http
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -040077from googleapiclient.http import BatchHttpRequest
John Asmuth864311d2014-04-24 15:46:08 -040078from googleapiclient.http import HttpMock
79from googleapiclient.http import HttpMockSequence
80from googleapiclient.http import MediaFileUpload
81from googleapiclient.http import MediaIoBaseUpload
82from googleapiclient.http import MediaUpload
83from googleapiclient.http import MediaUploadProgress
84from googleapiclient.http import tunnel_patch
Thomas Coffee20af04d2017-02-10 15:24:44 -080085from googleapiclient.model import JsonModel
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +090086from googleapiclient.schema import Schemas
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080087from oauth2client import GOOGLE_TOKEN_URI
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080088from oauth2client.client import OAuth2Credentials, GoogleCredentials
Joe Gregorio79daca02013-03-29 16:25:52 -040089
Bu Sun Kima9583f72021-03-15 09:12:02 -060090
Helen Koikede13e3b2018-04-26 16:05:16 -030091from googleapiclient import _helpers as util
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -070092
Joe Gregoriodc106fc2012-11-20 14:30:14 -050093import uritemplate
94
Joe Gregoriocb8103d2011-02-11 23:20:52 -050095
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070096DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
Joe Gregoriocb8103d2011-02-11 23:20:52 -050097
Joe Gregorioa98733f2011-09-16 10:12:28 -040098
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050099def assertUrisEqual(testcase, expected, actual):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700100 """Test that URIs are the same, up to reordering of query parameters."""
101 expected = urlparse(expected)
102 actual = urlparse(actual)
103 testcase.assertEqual(expected.scheme, actual.scheme)
104 testcase.assertEqual(expected.netloc, actual.netloc)
105 testcase.assertEqual(expected.path, actual.path)
106 testcase.assertEqual(expected.params, actual.params)
107 testcase.assertEqual(expected.fragment, actual.fragment)
108 expected_query = parse_qs(expected.query)
109 actual_query = parse_qs(actual.query)
110 for name in list(expected_query.keys()):
111 testcase.assertEqual(expected_query[name], actual_query[name])
112 for name in list(actual_query.keys()):
113 testcase.assertEqual(expected_query[name], actual_query[name])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500114
115
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700116def assert_discovery_uri(testcase, actual, service_name, version, discovery):
117 """Assert that discovery URI used was the one that was expected
118 for a given service and version."""
119 params = {"api": service_name, "apiVersion": version}
120 expanded_requested_uri = uritemplate.expand(discovery, params)
121 assertUrisEqual(testcase, expanded_requested_uri, actual)
122
123
Bu Sun Kim790e7022020-09-11 20:18:06 -0600124def validate_discovery_requests(testcase, http_mock, service_name, version, discovery):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700125 """Validates that there have > 0 calls to Http Discovery
126 and that LAST discovery URI used was the one that was expected
127 for a given service and version."""
128 testcase.assertTrue(len(http_mock.request_sequence) > 0)
129 if len(http_mock.request_sequence) > 0:
130 actual_uri = http_mock.request_sequence[-1][0]
Bu Sun Kim790e7022020-09-11 20:18:06 -0600131 assert_discovery_uri(testcase, actual_uri, service_name, version, discovery)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700132
133
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500134def datafile(filename):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700135 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400136
137
Bu Sun Kim790e7022020-09-11 20:18:06 -0600138def read_datafile(filename, mode="r"):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700139 with open(datafile(filename), mode=mode) as f:
140 return f.read()
141
142
Joe Gregorio504a17f2012-12-07 14:14:26 -0500143class SetupHttplib2(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700144 def test_retries(self):
145 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
146 self.assertEqual(1, httplib2.RETRIES)
Joe Gregorio504a17f2012-12-07 14:14:26 -0500147
148
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400149class Utilities(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700150 def setUp(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700151 self.zoo_root_desc = json.loads(read_datafile("zoo.json", "r"))
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700152 self.zoo_get_method_desc = self.zoo_root_desc["methods"]["query"]
153 self.zoo_animals_resource = self.zoo_root_desc["resources"]["animals"]
154 self.zoo_insert_method_desc = self.zoo_animals_resource["methods"]["insert"]
155 self.zoo_schema = Schemas(self.zoo_root_desc)
Daniel Hermesc2113242013-02-27 10:16:13 -0800156
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700157 def test_key2param(self):
158 self.assertEqual("max_results", key2param("max-results"))
159 self.assertEqual("x007_bond", key2param("007-bond"))
Daniel Hermesc2113242013-02-27 10:16:13 -0800160
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700161 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc, schema):
162 self.assertEqual(method_desc["httpMethod"], http_method)
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400163
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700164 method_desc_copy = copy.deepcopy(method_desc)
165 self.assertEqual(method_desc, method_desc_copy)
Daniel Hermesc2113242013-02-27 10:16:13 -0800166
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700167 parameters = _fix_up_parameters(
168 method_desc_copy, root_desc, http_method, schema
169 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800170
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700171 self.assertNotEqual(method_desc, method_desc_copy)
Daniel Hermesc2113242013-02-27 10:16:13 -0800172
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700173 for param_name in STACK_QUERY_PARAMETERS:
174 self.assertEqual(
175 STACK_QUERY_PARAMETER_DEFAULT_VALUE, parameters[param_name]
176 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800177
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700178 for param_name, value in six.iteritems(root_desc.get("parameters", {})):
179 self.assertEqual(value, parameters[param_name])
Daniel Hermesc2113242013-02-27 10:16:13 -0800180
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700181 return parameters
Daniel Hermesc2113242013-02-27 10:16:13 -0800182
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700183 def test_fix_up_parameters_get(self):
184 parameters = self._base_fix_up_parameters_test(
185 self.zoo_get_method_desc, "GET", self.zoo_root_desc, self.zoo_schema
186 )
187 # Since http_method is 'GET'
188 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800189
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700190 def test_fix_up_parameters_insert(self):
191 parameters = self._base_fix_up_parameters_test(
192 self.zoo_insert_method_desc, "POST", self.zoo_root_desc, self.zoo_schema
193 )
194 body = {"description": "The request body.", "type": "object", "$ref": "Animal"}
195 self.assertEqual(parameters["body"], body)
Daniel Hermesc2113242013-02-27 10:16:13 -0800196
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700197 def test_fix_up_parameters_check_body(self):
198 dummy_root_desc = {}
199 dummy_schema = {
200 "Request": {
201 "properties": {
202 "description": "Required. Dummy parameter.",
203 "type": "string",
204 }
205 }
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900206 }
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700207 no_payload_http_method = "DELETE"
208 with_payload_http_method = "PUT"
Daniel Hermesc2113242013-02-27 10:16:13 -0800209
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700210 invalid_method_desc = {"response": "Who cares"}
211 valid_method_desc = {
212 "request": {"key1": "value1", "key2": "value2", "$ref": "Request"}
213 }
Daniel Hermesc2113242013-02-27 10:16:13 -0800214
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700215 parameters = _fix_up_parameters(
216 invalid_method_desc, dummy_root_desc, no_payload_http_method, dummy_schema
217 )
218 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800219
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700220 parameters = _fix_up_parameters(
221 valid_method_desc, dummy_root_desc, no_payload_http_method, dummy_schema
222 )
223 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800224
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700225 parameters = _fix_up_parameters(
226 invalid_method_desc, dummy_root_desc, with_payload_http_method, dummy_schema
227 )
228 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800229
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700230 parameters = _fix_up_parameters(
231 valid_method_desc, dummy_root_desc, with_payload_http_method, dummy_schema
232 )
233 body = {
234 "description": "The request body.",
235 "type": "object",
236 "$ref": "Request",
237 "key1": "value1",
238 "key2": "value2",
239 }
240 self.assertEqual(parameters["body"], body)
Daniel Hermesc2113242013-02-27 10:16:13 -0800241
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700242 def test_fix_up_parameters_optional_body(self):
243 # Request with no parameters
244 dummy_schema = {"Request": {"properties": {}}}
245 method_desc = {"request": {"$ref": "Request"}}
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900246
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700247 parameters = _fix_up_parameters(method_desc, {}, "POST", dummy_schema)
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900248
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700249 def _base_fix_up_method_description_test(
250 self,
251 method_desc,
252 initial_parameters,
253 final_parameters,
254 final_accept,
255 final_max_size,
256 final_media_path_url,
257 ):
arithmetic1728981eadf2020-06-02 10:20:10 -0700258 fake_root_desc = {
259 "rootUrl": "http://root/",
260 "servicePath": "fake/",
261 "mtlsRootUrl": "http://root/",
262 }
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700263 fake_path_url = "fake-path/"
Daniel Hermesc2113242013-02-27 10:16:13 -0800264
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700265 accept, max_size, media_path_url = _fix_up_media_upload(
266 method_desc, fake_root_desc, fake_path_url, initial_parameters
267 )
268 self.assertEqual(accept, final_accept)
269 self.assertEqual(max_size, final_max_size)
270 self.assertEqual(media_path_url, final_media_path_url)
271 self.assertEqual(initial_parameters, final_parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800272
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700273 def test_fix_up_media_upload_no_initial_invalid(self):
274 invalid_method_desc = {"response": "Who cares"}
275 self._base_fix_up_method_description_test(
276 invalid_method_desc, {}, {}, [], 0, None
277 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800278
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700279 def test_fix_up_media_upload_no_initial_valid_minimal(self):
280 valid_method_desc = {"mediaUpload": {"accept": []}}
281 final_parameters = {
282 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
283 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
284 }
285 self._base_fix_up_method_description_test(
286 valid_method_desc,
287 {},
288 final_parameters,
289 [],
290 0,
291 "http://root/upload/fake/fake-path/",
292 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800293
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700294 def test_fix_up_media_upload_no_initial_valid_full(self):
295 valid_method_desc = {"mediaUpload": {"accept": ["*/*"], "maxSize": "10GB"}}
296 final_parameters = {
297 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
298 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
299 }
300 ten_gb = 10 * 2 ** 30
301 self._base_fix_up_method_description_test(
302 valid_method_desc,
303 {},
304 final_parameters,
305 ["*/*"],
306 ten_gb,
307 "http://root/upload/fake/fake-path/",
308 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800309
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700310 def test_fix_up_media_upload_with_initial_invalid(self):
311 invalid_method_desc = {"response": "Who cares"}
312 initial_parameters = {"body": {}}
313 self._base_fix_up_method_description_test(
314 invalid_method_desc, initial_parameters, initial_parameters, [], 0, None
315 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800316
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700317 def test_fix_up_media_upload_with_initial_valid_minimal(self):
318 valid_method_desc = {"mediaUpload": {"accept": []}}
319 initial_parameters = {"body": {}}
320 final_parameters = {
321 "body": {},
322 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
323 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
324 }
325 self._base_fix_up_method_description_test(
326 valid_method_desc,
327 initial_parameters,
328 final_parameters,
329 [],
330 0,
331 "http://root/upload/fake/fake-path/",
332 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800333
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700334 def test_fix_up_media_upload_with_initial_valid_full(self):
335 valid_method_desc = {"mediaUpload": {"accept": ["*/*"], "maxSize": "10GB"}}
336 initial_parameters = {"body": {}}
337 final_parameters = {
338 "body": {},
339 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
340 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
341 }
342 ten_gb = 10 * 2 ** 30
343 self._base_fix_up_method_description_test(
344 valid_method_desc,
345 initial_parameters,
346 final_parameters,
347 ["*/*"],
348 ten_gb,
349 "http://root/upload/fake/fake-path/",
350 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800351
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700352 def test_fix_up_method_description_get(self):
353 result = _fix_up_method_description(
354 self.zoo_get_method_desc, self.zoo_root_desc, self.zoo_schema
355 )
356 path_url = "query"
357 http_method = "GET"
358 method_id = "bigquery.query"
359 accept = []
360 max_size = 0
361 media_path_url = None
362 self.assertEqual(
363 result, (path_url, http_method, method_id, accept, max_size, media_path_url)
364 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800365
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700366 def test_fix_up_method_description_insert(self):
367 result = _fix_up_method_description(
368 self.zoo_insert_method_desc, self.zoo_root_desc, self.zoo_schema
369 )
370 path_url = "animals"
371 http_method = "POST"
372 method_id = "zoo.animals.insert"
373 accept = ["image/png"]
374 max_size = 1024
375 media_path_url = "https://www.googleapis.com/upload/zoo/v1/animals"
376 self.assertEqual(
377 result, (path_url, http_method, method_id, accept, max_size, media_path_url)
378 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800379
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700380 def test_urljoin(self):
381 # We want to exhaustively test various URL combinations.
382 simple_bases = ["https://www.googleapis.com", "https://www.googleapis.com/"]
383 long_urls = ["foo/v1/bar:custom?alt=json", "/foo/v1/bar:custom?alt=json"]
Craig Citro7ee535d2015-02-23 10:11:14 -0800384
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700385 long_bases = [
386 "https://www.googleapis.com/foo/v1",
387 "https://www.googleapis.com/foo/v1/",
388 ]
389 simple_urls = ["bar:custom?alt=json", "/bar:custom?alt=json"]
Craig Citro7ee535d2015-02-23 10:11:14 -0800390
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700391 final_url = "https://www.googleapis.com/foo/v1/bar:custom?alt=json"
392 for base, url in itertools.product(simple_bases, long_urls):
393 self.assertEqual(final_url, _urljoin(base, url))
394 for base, url in itertools.product(long_bases, simple_urls):
395 self.assertEqual(final_url, _urljoin(base, url))
Craig Citro7ee535d2015-02-23 10:11:14 -0800396
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700397 def test_ResourceMethodParameters_zoo_get(self):
398 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
Craig Citro7ee535d2015-02-23 10:11:14 -0800399
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700400 param_types = {
401 "a": "any",
402 "b": "boolean",
403 "e": "string",
404 "er": "string",
405 "i": "integer",
406 "n": "number",
407 "o": "object",
408 "q": "string",
409 "rr": "string",
410 }
411 keys = list(param_types.keys())
412 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
413 self.assertEqual(parameters.required_params, [])
414 self.assertEqual(sorted(parameters.repeated_params), ["er", "rr"])
415 self.assertEqual(parameters.pattern_params, {"rr": "[a-z]+"})
416 self.assertEqual(
417 sorted(parameters.query_params),
418 ["a", "b", "e", "er", "i", "n", "o", "q", "rr"],
419 )
420 self.assertEqual(parameters.path_params, set())
421 self.assertEqual(parameters.param_types, param_types)
422 enum_params = {"e": ["foo", "bar"], "er": ["one", "two", "three"]}
423 self.assertEqual(parameters.enum_params, enum_params)
Daniel Hermes954e1242013-02-28 09:28:37 -0800424
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700425 def test_ResourceMethodParameters_zoo_animals_patch(self):
426 method_desc = self.zoo_animals_resource["methods"]["patch"]
427 parameters = ResourceMethodParameters(method_desc)
Daniel Hermes954e1242013-02-28 09:28:37 -0800428
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700429 param_types = {"name": "string"}
430 keys = list(param_types.keys())
431 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
432 self.assertEqual(parameters.required_params, ["name"])
433 self.assertEqual(parameters.repeated_params, [])
434 self.assertEqual(parameters.pattern_params, {})
435 self.assertEqual(parameters.query_params, [])
436 self.assertEqual(parameters.path_params, set(["name"]))
437 self.assertEqual(parameters.param_types, param_types)
438 self.assertEqual(parameters.enum_params, {})
Daniel Hermes954e1242013-02-28 09:28:37 -0800439
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400440
Bu Sun Kim98888da2020-09-23 11:10:39 -0600441class Discovery(unittest.TestCase):
442 def test_discovery_http_is_closed(self):
443 http = HttpMock(datafile("malformed.json"), {"status": "200"})
444 service = build("plus", "v1", credentials=mock.sentinel.credentials)
445 http.close.assert_called_once()
446
447
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500448class DiscoveryErrors(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700449 def test_tests_should_be_run_with_strict_positional_enforcement(self):
450 try:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500451 plus = build("plus", "v1", None, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700452 self.fail("should have raised a TypeError exception over missing http=.")
453 except TypeError:
454 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500455
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700456 def test_failed_to_parse_discovery_json(self):
457 self.http = HttpMock(datafile("malformed.json"), {"status": "200"})
458 try:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500459 plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700460 self.fail("should have raised an exception over malformed JSON.")
461 except InvalidJsonError:
462 pass
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400463
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700464 def test_unknown_api_name_or_version(self):
465 http = HttpMockSequence(
466 [
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700467 ({"status": "404"}, read_datafile("zoo.json", "rb")),
468 ({"status": "404"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700469 ]
470 )
471 with self.assertRaises(UnknownApiNameOrVersion):
472 plus = build("plus", "v1", http=http, cache_discovery=False)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500473
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700474 def test_credentials_and_http_mutually_exclusive(self):
475 http = HttpMock(datafile("plus.json"), {"status": "200"})
476 with self.assertRaises(ValueError):
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500477 build("plus", "v1", http=http, credentials=mock.sentinel.credentials, static_discovery=False)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800478
Bu Sun Kim790e7022020-09-11 20:18:06 -0600479 def test_credentials_file_and_http_mutually_exclusive(self):
480 http = HttpMock(datafile("plus.json"), {"status": "200"})
481 with self.assertRaises(ValueError):
482 build(
483 "plus",
484 "v1",
485 http=http,
486 client_options=google.api_core.client_options.ClientOptions(
487 credentials_file="credentials.json"
488 ),
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500489 static_discovery=False,
Bu Sun Kim790e7022020-09-11 20:18:06 -0600490 )
491
492 def test_credentials_and_credentials_file_mutually_exclusive(self):
493 with self.assertRaises(google.api_core.exceptions.DuplicateCredentialArgs):
494 build(
495 "plus",
496 "v1",
497 credentials=mock.sentinel.credentials,
498 client_options=google.api_core.client_options.ClientOptions(
499 credentials_file="credentials.json"
500 ),
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500501 static_discovery=False,
Bu Sun Kim790e7022020-09-11 20:18:06 -0600502 )
503
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500504
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100505class DiscoveryFromDocument(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700506 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400507
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700508 def test_can_build_from_local_document(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700509 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700510 plus = build_from_document(
511 discovery,
512 base="https://www.googleapis.com/",
513 credentials=self.MOCK_CREDENTIALS,
514 )
arithmetic1728981eadf2020-06-02 10:20:10 -0700515 self.assertIsNotNone(plus)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700516 self.assertTrue(hasattr(plus, "activities"))
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500517
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700518 def test_can_build_from_local_deserialized_document(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700519 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700520 discovery = json.loads(discovery)
521 plus = build_from_document(
522 discovery,
523 base="https://www.googleapis.com/",
524 credentials=self.MOCK_CREDENTIALS,
525 )
arithmetic1728981eadf2020-06-02 10:20:10 -0700526 self.assertIsNotNone(plus)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700527 self.assertTrue(hasattr(plus, "activities"))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400528
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700529 def test_building_with_base_remembers_base(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700530 discovery = read_datafile("plus.json")
Joe Gregorioa98733f2011-09-16 10:12:28 -0400531
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700532 base = "https://www.example.com/"
533 plus = build_from_document(
534 discovery, base=base, credentials=self.MOCK_CREDENTIALS
535 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700536 self.assertEqual("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100537
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700538 def test_building_with_optional_http_with_authorization(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700539 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700540 plus = build_from_document(
541 discovery,
542 base="https://www.googleapis.com/",
543 credentials=self.MOCK_CREDENTIALS,
544 )
Igor Maravić22435292017-01-19 22:28:22 +0100545
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700546 # plus service requires Authorization, hence we expect to see AuthorizedHttp object here
547 self.assertIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
548 self.assertIsInstance(plus._http.http, httplib2.Http)
549 self.assertIsInstance(plus._http.http.timeout, int)
550 self.assertGreater(plus._http.http.timeout, 0)
Igor Maravić22435292017-01-19 22:28:22 +0100551
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700552 def test_building_with_optional_http_with_no_authorization(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700553 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700554 # Cleanup auth field, so we would use plain http client
555 discovery = json.loads(discovery)
556 discovery["auth"] = {}
557 discovery = json.dumps(discovery)
Igor Maravić22435292017-01-19 22:28:22 +0100558
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700559 plus = build_from_document(
560 discovery, base="https://www.googleapis.com/", credentials=None
561 )
562 # plus service requires Authorization
563 self.assertIsInstance(plus._http, httplib2.Http)
564 self.assertIsInstance(plus._http.timeout, int)
565 self.assertGreater(plus._http.timeout, 0)
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700566
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700567 def test_building_with_explicit_http(self):
568 http = HttpMock()
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700569 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700570 plus = build_from_document(
571 discovery, base="https://www.googleapis.com/", http=http
572 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700573 self.assertEqual(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100574
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700575 def test_building_with_developer_key_skips_adc(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700576 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700577 plus = build_from_document(
578 discovery, base="https://www.googleapis.com/", developerKey="123"
579 )
580 self.assertIsInstance(plus._http, httplib2.Http)
581 # It should not be an AuthorizedHttp, because that would indicate that
582 # application default credentials were used.
583 self.assertNotIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
Jon Wayne Parrott068eb352017-02-08 10:13:06 -0800584
Bu Sun Kim98888da2020-09-23 11:10:39 -0600585 def test_building_with_context_manager(self):
586 discovery = read_datafile("plus.json")
587 with mock.patch("httplib2.Http") as http:
588 with build_from_document(discovery, base="https://www.googleapis.com/", credentials=self.MOCK_CREDENTIALS) as plus:
589 self.assertIsNotNone(plus)
590 self.assertTrue(hasattr(plus, "activities"))
591 plus._http.http.close.assert_called_once()
592
593 def test_resource_close(self):
594 discovery = read_datafile("plus.json")
Anthonios Partheniou3b4f2e22021-03-19 11:36:01 -0400595
Bu Sun Kima9583f72021-03-15 09:12:02 -0600596 with mock.patch("httplib2.Http", autospec=True) as httplib2_http:
597 http = httplib2_http()
598 plus = build_from_document(
599 discovery,
600 base="https://www.googleapis.com/",
601 http=http,
602 )
603 plus.close()
604 http.close.assert_called_once()
605
606 def test_resource_close_authorized_http(self):
607 discovery = read_datafile("plus.json")
608 with mock.patch("google_auth_httplib2.AuthorizedHttp", autospec=True):
Bu Sun Kim98888da2020-09-23 11:10:39 -0600609 plus = build_from_document(
610 discovery,
611 base="https://www.googleapis.com/",
612 credentials=self.MOCK_CREDENTIALS,
613 )
614 plus.close()
Bu Sun Kima9583f72021-03-15 09:12:02 -0600615 plus._http.close.assert_called_once()
Bu Sun Kim98888da2020-09-23 11:10:39 -0600616
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700617 def test_api_endpoint_override_from_client_options(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700618 discovery = read_datafile("plus.json")
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700619 api_endpoint = "https://foo.googleapis.com/"
620 options = google.api_core.client_options.ClientOptions(
621 api_endpoint=api_endpoint
622 )
Bu Sun Kim8ed1dcd2020-05-14 00:42:22 -0700623 plus = build_from_document(
arithmetic1728981eadf2020-06-02 10:20:10 -0700624 discovery, client_options=options, credentials=self.MOCK_CREDENTIALS
Bu Sun Kim8ed1dcd2020-05-14 00:42:22 -0700625 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700626
627 self.assertEqual(plus._baseUrl, api_endpoint)
628
Pavel Kiselev21af37b2020-06-18 19:50:03 +0300629 def test_api_endpoint_override_from_client_options_mapping_object(self):
630
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700631 discovery = read_datafile("plus.json")
Pavel Kiselev21af37b2020-06-18 19:50:03 +0300632 api_endpoint = "https://foo.googleapis.com/"
633 mapping_object = defaultdict(str)
Bu Sun Kim790e7022020-09-11 20:18:06 -0600634 mapping_object["api_endpoint"] = api_endpoint
635 plus = build_from_document(discovery, client_options=mapping_object)
Pavel Kiselev21af37b2020-06-18 19:50:03 +0300636
637 self.assertEqual(plus._baseUrl, api_endpoint)
638
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700639 def test_api_endpoint_override_from_client_options_dict(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700640 discovery = read_datafile("plus.json")
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700641 api_endpoint = "https://foo.googleapis.com/"
642 plus = build_from_document(
arithmetic1728981eadf2020-06-02 10:20:10 -0700643 discovery,
Bu Sun Kim8ed1dcd2020-05-14 00:42:22 -0700644 client_options={"api_endpoint": api_endpoint},
arithmetic1728981eadf2020-06-02 10:20:10 -0700645 credentials=self.MOCK_CREDENTIALS,
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700646 )
647
648 self.assertEqual(plus._baseUrl, api_endpoint)
649
Bu Sun Kim790e7022020-09-11 20:18:06 -0600650 def test_scopes_from_client_options(self):
651 discovery = read_datafile("plus.json")
652
653 with mock.patch("googleapiclient._auth.default_credentials") as default:
654 plus = build_from_document(
655 discovery, client_options={"scopes": ["1", "2"]},
656 )
657
658 default.assert_called_once_with(scopes=["1", "2"], quota_project_id=None)
659
660 def test_quota_project_from_client_options(self):
661 discovery = read_datafile("plus.json")
662
663 with mock.patch("googleapiclient._auth.default_credentials") as default:
664 plus = build_from_document(
665 discovery,
666 client_options=google.api_core.client_options.ClientOptions(
667 quota_project_id="my-project"
668 ),
669 )
670
671 default.assert_called_once_with(scopes=None, quota_project_id="my-project")
672
673 def test_credentials_file_from_client_options(self):
674 discovery = read_datafile("plus.json")
675
676 with mock.patch("googleapiclient._auth.credentials_from_file") as default:
677 plus = build_from_document(
678 discovery,
679 client_options=google.api_core.client_options.ClientOptions(
680 credentials_file="credentials.json"
681 ),
682 )
683
684 default.assert_called_once_with(
685 "credentials.json", scopes=None, quota_project_id=None
686 )
687
Jon Wayne Parrott068eb352017-02-08 10:13:06 -0800688
arithmetic1728981eadf2020-06-02 10:20:10 -0700689REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/"
690MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/"
691
692
693class DiscoveryFromDocumentMutualTLS(unittest.TestCase):
694 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
695 ADC_CERT_PATH = "adc_cert_path"
696 ADC_KEY_PATH = "adc_key_path"
697 ADC_PASSPHRASE = "adc_passphrase"
698
arithmetic17282fc5ca12020-08-27 14:08:12 -0700699 def check_http_client_cert(self, resource, has_client_cert="false"):
arithmetic1728981eadf2020-06-02 10:20:10 -0700700 if isinstance(resource._http, google_auth_httplib2.AuthorizedHttp):
701 certs = list(resource._http.http.certificates.iter(""))
702 else:
703 certs = list(resource._http.certificates.iter(""))
arithmetic17282fc5ca12020-08-27 14:08:12 -0700704 if has_client_cert == "true":
arithmetic1728981eadf2020-06-02 10:20:10 -0700705 self.assertEqual(len(certs), 1)
706 self.assertEqual(
707 certs[0], (self.ADC_KEY_PATH, self.ADC_CERT_PATH, self.ADC_PASSPHRASE)
708 )
709 else:
710 self.assertEqual(len(certs), 0)
711
712 def client_encrypted_cert_source(self):
713 return self.ADC_CERT_PATH, self.ADC_KEY_PATH, self.ADC_PASSPHRASE
714
arithmetic17282fc5ca12020-08-27 14:08:12 -0700715 @parameterized.expand(
716 [
717 ("never", "true"),
718 ("auto", "true"),
719 ("always", "true"),
720 ("never", "false"),
721 ("auto", "false"),
722 ("always", "false"),
723 ]
724 )
725 def test_mtls_not_trigger_if_http_provided(self, use_mtls_env, use_client_cert):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700726 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700727
arithmetic17282fc5ca12020-08-27 14:08:12 -0700728 with mock.patch.dict(
729 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
730 ):
731 with mock.patch.dict(
732 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
733 ):
734 plus = build_from_document(discovery, http=httplib2.Http())
735 self.assertIsNotNone(plus)
736 self.assertEqual(plus._baseUrl, REGULAR_ENDPOINT)
737 self.check_http_client_cert(plus, has_client_cert="false")
arithmetic1728981eadf2020-06-02 10:20:10 -0700738
739 @parameterized.expand(
740 [
arithmetic17282fc5ca12020-08-27 14:08:12 -0700741 ("never", "true"),
742 ("auto", "true"),
743 ("always", "true"),
744 ("never", "false"),
745 ("auto", "false"),
746 ("always", "false"),
arithmetic1728981eadf2020-06-02 10:20:10 -0700747 ]
748 )
arithmetic17282fc5ca12020-08-27 14:08:12 -0700749 def test_exception_with_client_cert_source(self, use_mtls_env, use_client_cert):
750 discovery = read_datafile("plus.json")
751 with mock.patch.dict(
752 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
753 ):
754 with mock.patch.dict(
755 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
756 ):
757 with self.assertRaises(MutualTLSChannelError):
758 build_from_document(
759 discovery,
760 credentials=self.MOCK_CREDENTIALS,
761 client_options={"client_cert_source": mock.Mock()},
762 )
763
764 @parameterized.expand(
765 [
766 ("never", "true", REGULAR_ENDPOINT),
767 ("auto", "true", MTLS_ENDPOINT),
768 ("always", "true", MTLS_ENDPOINT),
769 ("never", "false", REGULAR_ENDPOINT),
770 ("auto", "false", REGULAR_ENDPOINT),
771 ("always", "false", MTLS_ENDPOINT),
772 ]
773 )
774 def test_mtls_with_provided_client_cert(
775 self, use_mtls_env, use_client_cert, base_url
776 ):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700777 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700778
arithmetic17282fc5ca12020-08-27 14:08:12 -0700779 with mock.patch.dict(
780 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
781 ):
782 with mock.patch.dict(
783 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
784 ):
785 plus = build_from_document(
786 discovery,
787 credentials=self.MOCK_CREDENTIALS,
788 client_options={
789 "client_encrypted_cert_source": self.client_encrypted_cert_source
790 },
791 )
792 self.assertIsNotNone(plus)
793 self.check_http_client_cert(plus, has_client_cert=use_client_cert)
794 self.assertEqual(plus._baseUrl, base_url)
arithmetic1728981eadf2020-06-02 10:20:10 -0700795
arithmetic17282fc5ca12020-08-27 14:08:12 -0700796 @parameterized.expand(
797 [
798 ("never", "true"),
799 ("auto", "true"),
800 ("always", "true"),
801 ("never", "false"),
802 ("auto", "false"),
803 ("always", "false"),
804 ]
805 )
806 def test_endpoint_not_switch(self, use_mtls_env, use_client_cert):
arithmetic1728981eadf2020-06-02 10:20:10 -0700807 # Test endpoint is not switched if user provided api endpoint
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700808 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700809
arithmetic17282fc5ca12020-08-27 14:08:12 -0700810 with mock.patch.dict(
811 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
812 ):
813 with mock.patch.dict(
814 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
815 ):
816 plus = build_from_document(
817 discovery,
818 credentials=self.MOCK_CREDENTIALS,
819 client_options={
820 "api_endpoint": "https://foo.googleapis.com",
821 "client_encrypted_cert_source": self.client_encrypted_cert_source,
822 },
823 )
824 self.assertIsNotNone(plus)
825 self.check_http_client_cert(plus, has_client_cert=use_client_cert)
826 self.assertEqual(plus._baseUrl, "https://foo.googleapis.com")
arithmetic1728981eadf2020-06-02 10:20:10 -0700827
828 @parameterized.expand(
829 [
arithmetic17282fc5ca12020-08-27 14:08:12 -0700830 ("never", "true", REGULAR_ENDPOINT),
831 ("auto", "true", MTLS_ENDPOINT),
832 ("always", "true", MTLS_ENDPOINT),
833 ("never", "false", REGULAR_ENDPOINT),
834 ("auto", "false", REGULAR_ENDPOINT),
835 ("always", "false", MTLS_ENDPOINT),
arithmetic1728981eadf2020-06-02 10:20:10 -0700836 ]
837 )
838 @mock.patch(
839 "google.auth.transport.mtls.has_default_client_cert_source", autospec=True
840 )
841 @mock.patch(
842 "google.auth.transport.mtls.default_client_encrypted_cert_source", autospec=True
843 )
844 def test_mtls_with_default_client_cert(
845 self,
846 use_mtls_env,
arithmetic17282fc5ca12020-08-27 14:08:12 -0700847 use_client_cert,
arithmetic1728981eadf2020-06-02 10:20:10 -0700848 base_url,
849 default_client_encrypted_cert_source,
850 has_default_client_cert_source,
851 ):
852 has_default_client_cert_source.return_value = True
853 default_client_encrypted_cert_source.return_value = (
854 self.client_encrypted_cert_source
855 )
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700856 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700857
arithmetic17282fc5ca12020-08-27 14:08:12 -0700858 with mock.patch.dict(
859 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
860 ):
861 with mock.patch.dict(
862 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
863 ):
864 plus = build_from_document(
865 discovery,
866 credentials=self.MOCK_CREDENTIALS,
867 adc_cert_path=self.ADC_CERT_PATH,
868 adc_key_path=self.ADC_KEY_PATH,
869 )
870 self.assertIsNotNone(plus)
871 self.check_http_client_cert(plus, has_client_cert=use_client_cert)
872 self.assertEqual(plus._baseUrl, base_url)
arithmetic1728981eadf2020-06-02 10:20:10 -0700873
874 @parameterized.expand(
875 [
arithmetic17282fc5ca12020-08-27 14:08:12 -0700876 ("never", "true", REGULAR_ENDPOINT),
877 ("auto", "true", REGULAR_ENDPOINT),
878 ("always", "true", MTLS_ENDPOINT),
879 ("never", "false", REGULAR_ENDPOINT),
880 ("auto", "false", REGULAR_ENDPOINT),
881 ("always", "false", MTLS_ENDPOINT),
arithmetic1728981eadf2020-06-02 10:20:10 -0700882 ]
883 )
884 @mock.patch(
885 "google.auth.transport.mtls.has_default_client_cert_source", autospec=True
886 )
887 def test_mtls_with_no_client_cert(
arithmetic17282fc5ca12020-08-27 14:08:12 -0700888 self, use_mtls_env, use_client_cert, base_url, has_default_client_cert_source
arithmetic1728981eadf2020-06-02 10:20:10 -0700889 ):
890 has_default_client_cert_source.return_value = False
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700891 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700892
arithmetic17282fc5ca12020-08-27 14:08:12 -0700893 with mock.patch.dict(
894 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
895 ):
896 with mock.patch.dict(
897 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
898 ):
899 plus = build_from_document(
900 discovery,
901 credentials=self.MOCK_CREDENTIALS,
902 adc_cert_path=self.ADC_CERT_PATH,
903 adc_key_path=self.ADC_KEY_PATH,
904 )
905 self.assertIsNotNone(plus)
906 self.check_http_client_cert(plus, has_client_cert="false")
907 self.assertEqual(plus._baseUrl, base_url)
arithmetic1728981eadf2020-06-02 10:20:10 -0700908
909
Joe Gregorioa98733f2011-09-16 10:12:28 -0400910class DiscoveryFromHttp(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700911 def setUp(self):
912 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400913
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700914 def tearDown(self):
915 os.environ = self.old_environ
Joe Gregorio583d9e42011-09-16 15:54:15 -0400916
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700917 def test_userip_is_added_to_discovery_uri(self):
918 # build() will raise an HttpError on a 400, use this to pick the request uri
919 # out of the raised exception.
920 os.environ["REMOTE_ADDR"] = "10.0.0.1"
921 try:
922 http = HttpMockSequence(
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700923 [({"status": "400"}, read_datafile("zoo.json", "rb"))]
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700924 )
925 zoo = build(
926 "zoo",
927 "v1",
928 http=http,
929 developerKey=None,
Anthonios Partheniou3b4f2e22021-03-19 11:36:01 -0400930 discoveryServiceUrl="http://example.com"
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700931 )
932 self.fail("Should have raised an exception.")
933 except HttpError as e:
934 self.assertEqual(e.uri, "http://example.com?userIp=10.0.0.1")
Joe Gregorioa98733f2011-09-16 10:12:28 -0400935
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700936 def test_userip_missing_is_not_added_to_discovery_uri(self):
937 # build() will raise an HttpError on a 400, use this to pick the request uri
938 # out of the raised exception.
939 try:
940 http = HttpMockSequence(
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700941 [({"status": "400"}, read_datafile("zoo.json", "rb"))]
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700942 )
943 zoo = build(
944 "zoo",
945 "v1",
946 http=http,
947 developerKey=None,
Anthonios Partheniou3b4f2e22021-03-19 11:36:01 -0400948 discoveryServiceUrl="http://example.com"
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700949 )
950 self.fail("Should have raised an exception.")
951 except HttpError as e:
952 self.assertEqual(e.uri, "http://example.com")
Joe Gregorioa98733f2011-09-16 10:12:28 -0400953
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700954 def test_key_is_added_to_discovery_uri(self):
955 # build() will raise an HttpError on a 400, use this to pick the request uri
956 # out of the raised exception.
957 try:
958 http = HttpMockSequence(
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700959 [({"status": "400"}, read_datafile("zoo.json", "rb"))]
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700960 )
961 zoo = build(
962 "zoo",
963 "v1",
964 http=http,
965 developerKey="foo",
966 discoveryServiceUrl="http://example.com",
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500967 static_discovery=False,
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700968 )
969 self.fail("Should have raised an exception.")
970 except HttpError as e:
971 self.assertEqual(e.uri, "http://example.com?key=foo")
Arunpn9d779cc2018-11-30 10:25:01 -0800972
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700973 def test_discovery_loading_from_v2_discovery_uri(self):
974 http = HttpMockSequence(
975 [
976 ({"status": "404"}, "Not found"),
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700977 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700978 ]
979 )
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500980 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700981 self.assertTrue(hasattr(zoo, "animals"))
982
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700983 def test_api_endpoint_override_from_client_options(self):
984 http = HttpMockSequence(
985 [
986 ({"status": "404"}, "Not found"),
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700987 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700988 ]
989 )
990 api_endpoint = "https://foo.googleapis.com/"
991 options = google.api_core.client_options.ClientOptions(
992 api_endpoint=api_endpoint
993 )
994 zoo = build(
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500995 "zoo", "v1", http=http, cache_discovery=False, client_options=options, static_discovery=False
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700996 )
997 self.assertEqual(zoo._baseUrl, api_endpoint)
998
999 def test_api_endpoint_override_from_client_options_dict(self):
1000 http = HttpMockSequence(
1001 [
1002 ({"status": "404"}, "Not found"),
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001003 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001004 ]
1005 )
1006 api_endpoint = "https://foo.googleapis.com/"
1007 zoo = build(
1008 "zoo",
1009 "v1",
1010 http=http,
1011 cache_discovery=False,
1012 client_options={"api_endpoint": api_endpoint},
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001013 static_discovery=False,
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001014 )
1015 self.assertEqual(zoo._baseUrl, api_endpoint)
1016
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001017 def test_discovery_with_empty_version_uses_v2(self):
Bu Sun Kim790e7022020-09-11 20:18:06 -06001018 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),])
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001019 build("zoo", version=None, http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001020 validate_discovery_requests(self, http, "zoo", None, V2_DISCOVERY_URI)
1021
1022 def test_discovery_with_empty_version_preserves_custom_uri(self):
Bu Sun Kim790e7022020-09-11 20:18:06 -06001023 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),])
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001024 custom_discovery_uri = "https://foo.bar/$discovery"
1025 build(
Bu Sun Kim790e7022020-09-11 20:18:06 -06001026 "zoo",
1027 version=None,
1028 http=http,
1029 cache_discovery=False,
1030 discoveryServiceUrl=custom_discovery_uri,
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001031 static_discovery=False,
Bu Sun Kim790e7022020-09-11 20:18:06 -06001032 )
1033 validate_discovery_requests(self, http, "zoo", None, custom_discovery_uri)
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001034
1035 def test_discovery_with_valid_version_uses_v1(self):
Bu Sun Kim790e7022020-09-11 20:18:06 -06001036 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),])
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001037 build("zoo", version="v123", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001038 validate_discovery_requests(self, http, "zoo", "v123", V1_DISCOVERY_URI)
1039
Joe Gregorioa98733f2011-09-16 10:12:28 -04001040
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001041class DiscoveryRetryFromHttp(unittest.TestCase):
1042 def test_repeated_500_retries_and_fails(self):
1043 http = HttpMockSequence(
1044 [
1045 ({"status": "500"}, read_datafile("500.json", "rb")),
1046 ({"status": "503"}, read_datafile("503.json", "rb")),
1047 ]
1048 )
1049 with self.assertRaises(HttpError):
1050 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001051 build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001052
1053 mocked_sleep.assert_called_once()
1054 # We also want to verify that we stayed with v1 discovery
1055 validate_discovery_requests(self, http, "zoo", "v1", V1_DISCOVERY_URI)
1056
1057 def test_v2_repeated_500_retries_and_fails(self):
1058 http = HttpMockSequence(
1059 [
1060 ({"status": "404"}, "Not found"), # last v1 discovery call
1061 ({"status": "500"}, read_datafile("500.json", "rb")),
1062 ({"status": "503"}, read_datafile("503.json", "rb")),
1063 ]
1064 )
1065 with self.assertRaises(HttpError):
1066 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001067 build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001068
1069 mocked_sleep.assert_called_once()
1070 # We also want to verify that we switched to v2 discovery
1071 validate_discovery_requests(self, http, "zoo", "v1", V2_DISCOVERY_URI)
1072
1073 def test_single_500_retries_and_succeeds(self):
1074 http = HttpMockSequence(
1075 [
1076 ({"status": "500"}, read_datafile("500.json", "rb")),
1077 ({"status": "200"}, read_datafile("zoo.json", "rb")),
1078 ]
1079 )
1080 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001081 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001082
1083 self.assertTrue(hasattr(zoo, "animals"))
1084 mocked_sleep.assert_called_once()
1085 # We also want to verify that we stayed with v1 discovery
1086 validate_discovery_requests(self, http, "zoo", "v1", V1_DISCOVERY_URI)
1087
1088 def test_single_500_then_404_retries_and_succeeds(self):
1089 http = HttpMockSequence(
1090 [
1091 ({"status": "500"}, read_datafile("500.json", "rb")),
1092 ({"status": "404"}, "Not found"), # last v1 discovery call
1093 ({"status": "200"}, read_datafile("zoo.json", "rb")),
1094 ]
1095 )
1096 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001097 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001098
1099 self.assertTrue(hasattr(zoo, "animals"))
1100 mocked_sleep.assert_called_once()
1101 # We also want to verify that we switched to v2 discovery
1102 validate_discovery_requests(self, http, "zoo", "v1", V2_DISCOVERY_URI)
1103
1104
Takashi Matsuo30125122015-08-19 11:42:32 -07001105class DiscoveryFromAppEngineCache(unittest.TestCase):
Zev Goldstein09e64472020-05-14 16:29:20 -04001106 def setUp(self):
1107 self.old_environ = os.environ.copy()
1108 os.environ["APPENGINE_RUNTIME"] = "python27"
1109
1110 def tearDown(self):
1111 os.environ = self.old_environ
1112
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001113 def test_appengine_memcache(self):
1114 # Hack module import
1115 self.orig_import = __import__
1116 self.mocked_api = mock.MagicMock()
Takashi Matsuo30125122015-08-19 11:42:32 -07001117
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001118 def import_mock(name, *args, **kwargs):
1119 if name == "google.appengine.api":
1120 return self.mocked_api
1121 return self.orig_import(name, *args, **kwargs)
Takashi Matsuo30125122015-08-19 11:42:32 -07001122
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001123 import_fullname = "__builtin__.__import__"
1124 if sys.version_info[0] >= 3:
1125 import_fullname = "builtins.__import__"
Takashi Matsuo30125122015-08-19 11:42:32 -07001126
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001127 with mock.patch(import_fullname, side_effect=import_mock):
1128 namespace = "google-api-client"
1129 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001130
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001131 self.mocked_api.memcache.get.return_value = None
Takashi Matsuo30125122015-08-19 11:42:32 -07001132
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001133 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001134
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001135 # memcache.get is called once
1136 url = "https://www.googleapis.com/discovery/v1/apis/plus/v1/rest"
1137 self.mocked_api.memcache.get.assert_called_once_with(
1138 url, namespace=namespace
1139 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001140
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001141 # memcache.set is called once
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001142 content = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001143 self.mocked_api.memcache.set.assert_called_once_with(
1144 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace
1145 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001146
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001147 # Returns the cached content this time.
1148 self.mocked_api.memcache.get.return_value = content
Takashi Matsuo30125122015-08-19 11:42:32 -07001149
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001150 # Make sure the contents are returned from the cache.
1151 # (Otherwise it should through an error)
1152 self.http = HttpMock(None, {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001153
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001154 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001155
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001156 # memcache.get is called twice
1157 self.mocked_api.memcache.get.assert_has_calls(
1158 [
1159 mock.call(url, namespace=namespace),
1160 mock.call(url, namespace=namespace),
1161 ]
1162 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001163
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001164 # memcahce.set is called just once
1165 self.mocked_api.memcache.set.assert_called_once_with(
1166 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace
1167 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001168
1169
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001170class DiscoveryFromStaticDocument(unittest.TestCase):
1171 def test_retrieve_from_local_when_static_discovery_true(self):
1172 http = HttpMockSequence([({"status": "400"}, "")])
1173 drive = build("drive", "v3", http=http, cache_discovery=False,
1174 static_discovery=True)
1175 self.assertIsNotNone(drive)
1176 self.assertTrue(hasattr(drive, "files"))
1177
1178 def test_retrieve_from_internet_when_static_discovery_false(self):
1179 http = HttpMockSequence([({"status": "400"}, "")])
1180 with self.assertRaises(HttpError):
1181 build("drive", "v3", http=http, cache_discovery=False,
1182 static_discovery=False)
1183
1184 def test_unknown_api_when_static_discovery_true(self):
1185 with self.assertRaises(UnknownApiNameOrVersion):
1186 build("doesnotexist", "v3", cache_discovery=False,
1187 static_discovery=True)
1188
1189
Takashi Matsuo30125122015-08-19 11:42:32 -07001190class DictCache(Cache):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001191 def __init__(self):
1192 self.d = {}
1193
1194 def get(self, url):
1195 return self.d.get(url, None)
1196
1197 def set(self, url, content):
1198 self.d[url] = content
1199
1200 def contains(self, url):
1201 return url in self.d
Takashi Matsuo30125122015-08-19 11:42:32 -07001202
1203
1204class DiscoveryFromFileCache(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001205 def test_file_based_cache(self):
1206 cache = mock.Mock(wraps=DictCache())
1207 with mock.patch(
1208 "googleapiclient.discovery_cache.autodetect", return_value=cache
1209 ):
1210 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001211
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001212 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001213
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001214 # cache.get is called once
1215 url = "https://www.googleapis.com/discovery/v1/apis/plus/v1/rest"
1216 cache.get.assert_called_once_with(url)
Takashi Matsuo30125122015-08-19 11:42:32 -07001217
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001218 # cache.set is called once
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001219 content = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001220 cache.set.assert_called_once_with(url, content)
Takashi Matsuo30125122015-08-19 11:42:32 -07001221
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001222 # Make sure there is a cache entry for the plus v1 discovery doc.
1223 self.assertTrue(cache.contains(url))
Takashi Matsuo30125122015-08-19 11:42:32 -07001224
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001225 # Make sure the contents are returned from the cache.
1226 # (Otherwise it should through an error)
1227 self.http = HttpMock(None, {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001228
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001229 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001230
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001231 # cache.get is called twice
1232 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
Takashi Matsuo30125122015-08-19 11:42:32 -07001233
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001234 # cahce.set is called just once
1235 cache.set.assert_called_once_with(url, content)
Takashi Matsuo30125122015-08-19 11:42:32 -07001236
1237
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001238class Discovery(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001239 def test_method_error_checking(self):
1240 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001241 plus = build("plus", "v1", http=self.http, static_discovery=False)
Joe Gregorio2379ecc2010-10-26 10:51:28 -04001242
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001243 # Missing required parameters
1244 try:
1245 plus.activities().list()
1246 self.fail()
1247 except TypeError as e:
1248 self.assertTrue("Missing" in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001249
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001250 # Missing required parameters even if supplied as None.
1251 try:
1252 plus.activities().list(collection=None, userId=None)
1253 self.fail()
1254 except TypeError as e:
1255 self.assertTrue("Missing" in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001256
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001257 # Parameter doesn't match regex
1258 try:
1259 plus.activities().list(collection="not_a_collection_name", userId="me")
1260 self.fail()
1261 except TypeError as e:
1262 self.assertTrue("not an allowed value" in str(e))
Joe Gregorio2467afa2012-06-20 12:21:25 -04001263
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001264 # Unexpected parameter
1265 try:
1266 plus.activities().list(flubber=12)
1267 self.fail()
1268 except TypeError as e:
1269 self.assertTrue("unexpected" in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001270
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001271 def _check_query_types(self, request):
1272 parsed = urlparse(request.uri)
1273 q = parse_qs(parsed[4])
1274 self.assertEqual(q["q"], ["foo"])
1275 self.assertEqual(q["i"], ["1"])
1276 self.assertEqual(q["n"], ["1.0"])
1277 self.assertEqual(q["b"], ["false"])
1278 self.assertEqual(q["a"], ["[1, 2, 3]"])
1279 self.assertEqual(q["o"], ["{'a': 1}"])
1280 self.assertEqual(q["e"], ["bar"])
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001281
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001282 def test_type_coercion(self):
1283 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001284 zoo = build("zoo", "v1", http=http, static_discovery=False)
Joe Gregoriobee86832011-02-22 10:00:19 -05001285
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001286 request = zoo.query(
1287 q="foo", i=1.0, n=1.0, b=0, a=[1, 2, 3], o={"a": 1}, e="bar"
1288 )
1289 self._check_query_types(request)
1290 request = zoo.query(
1291 q="foo", i=1, n=1, b=False, a=[1, 2, 3], o={"a": 1}, e="bar"
1292 )
1293 self._check_query_types(request)
Joe Gregoriobee86832011-02-22 10:00:19 -05001294
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001295 request = zoo.query(
1296 q="foo", i="1", n="1", b="", a=[1, 2, 3], o={"a": 1}, e="bar", er="two"
1297 )
Joe Gregoriof863f7a2011-02-24 03:24:44 -05001298
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001299 request = zoo.query(
1300 q="foo",
1301 i="1",
1302 n="1",
1303 b="",
1304 a=[1, 2, 3],
1305 o={"a": 1},
1306 e="bar",
1307 er=["one", "three"],
1308 rr=["foo", "bar"],
1309 )
1310 self._check_query_types(request)
Joe Gregorio6804c7a2011-11-18 14:30:32 -05001311
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001312 # Five is right out.
1313 self.assertRaises(TypeError, zoo.query, er=["one", "five"])
Joe Gregoriobee86832011-02-22 10:00:19 -05001314
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001315 def test_optional_stack_query_parameters(self):
1316 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001317 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001318 request = zoo.query(trace="html", fields="description")
Craig Citro1e742822012-03-01 12:59:22 -08001319
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001320 parsed = urlparse(request.uri)
1321 q = parse_qs(parsed[4])
1322 self.assertEqual(q["trace"], ["html"])
1323 self.assertEqual(q["fields"], ["description"])
Joe Gregorio13217952011-02-22 15:37:38 -05001324
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001325 def test_string_params_value_of_none_get_dropped(self):
1326 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001327 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001328 request = zoo.query(trace=None, fields="description")
Joe Gregoriof4153422011-03-18 22:45:18 -04001329
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001330 parsed = urlparse(request.uri)
1331 q = parse_qs(parsed[4])
1332 self.assertFalse("trace" in q)
Joe Gregorio2467afa2012-06-20 12:21:25 -04001333
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001334 def test_model_added_query_parameters(self):
1335 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001336 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001337 request = zoo.animals().get(name="Lion")
Joe Gregorio4b4002f2012-06-14 15:41:01 -04001338
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001339 parsed = urlparse(request.uri)
1340 q = parse_qs(parsed[4])
1341 self.assertEqual(q["alt"], ["json"])
1342 self.assertEqual(request.headers["accept"], "application/json")
Joe Gregorioe08a1662011-12-07 09:48:22 -05001343
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001344 def test_fallback_to_raw_model(self):
1345 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001346 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001347 request = zoo.animals().getmedia(name="Lion")
Joe Gregorioe08a1662011-12-07 09:48:22 -05001348
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001349 parsed = urlparse(request.uri)
1350 q = parse_qs(parsed[4])
1351 self.assertTrue("alt" not in q)
1352 self.assertEqual(request.headers["accept"], "*/*")
Joe Gregorioe08a1662011-12-07 09:48:22 -05001353
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001354 def test_patch(self):
1355 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001356 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001357 request = zoo.animals().patch(name="lion", body='{"description": "foo"}')
Joe Gregorioe08a1662011-12-07 09:48:22 -05001358
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001359 self.assertEqual(request.method, "PATCH")
Joe Gregoriof4153422011-03-18 22:45:18 -04001360
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001361 def test_batch_request_from_discovery(self):
1362 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
1363 # zoo defines a batchPath
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001364 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001365 batch_request = zoo.new_batch_http_request()
1366 self.assertEqual(
1367 batch_request._batch_uri, "https://www.googleapis.com/batchZoo"
1368 )
Joe Gregoriof4153422011-03-18 22:45:18 -04001369
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001370 def test_batch_request_from_default(self):
1371 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
1372 # plus does not define a batchPath
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001373 plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001374 batch_request = plus.new_batch_http_request()
1375 self.assertEqual(batch_request._batch_uri, "https://www.googleapis.com/batch")
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001376
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001377 def test_tunnel_patch(self):
1378 http = HttpMockSequence(
1379 [
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001380 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001381 ({"status": "200"}, "echo_request_headers_as_json"),
1382 ]
1383 )
1384 http = tunnel_patch(http)
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001385 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001386 resp = zoo.animals().patch(name="lion", body='{"description": "foo"}').execute()
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001387
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001388 self.assertTrue("x-http-method-override" in resp)
Joe Gregoriof4153422011-03-18 22:45:18 -04001389
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001390 def test_plus_resources(self):
1391 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001392 plus = build("plus", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001393 self.assertTrue(getattr(plus, "activities"))
1394 self.assertTrue(getattr(plus, "people"))
Joe Gregorioca876e42011-02-22 19:39:42 -05001395
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001396 def test_oauth2client_credentials(self):
1397 credentials = mock.Mock(spec=GoogleCredentials)
1398 credentials.create_scoped_required.return_value = False
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001399
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001400 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001401 service = build_from_document(discovery, credentials=credentials)
1402 self.assertEqual(service._http, credentials.authorize.return_value)
Orest Bolohane92c9002014-05-30 11:15:43 -07001403
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001404 def test_google_auth_credentials(self):
1405 credentials = mock.Mock(spec=google.auth.credentials.Credentials)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001406 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001407 service = build_from_document(discovery, credentials=credentials)
Orest Bolohane92c9002014-05-30 11:15:43 -07001408
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001409 self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp)
1410 self.assertEqual(service._http.credentials, credentials)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -08001411
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001412 def test_no_scopes_no_credentials(self):
1413 # Zoo doesn't have scopes
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001414 discovery = read_datafile("zoo.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001415 service = build_from_document(discovery)
1416 # Should be an ordinary httplib2.Http instance and not AuthorizedHttp.
1417 self.assertIsInstance(service._http, httplib2.Http)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -08001418
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001419 def test_full_featured(self):
1420 # Zoo should exercise all discovery facets
1421 # and should also have no future.json file.
1422 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001423 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001424 self.assertTrue(getattr(zoo, "animals"))
Orest Bolohane92c9002014-05-30 11:15:43 -07001425
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001426 request = zoo.animals().list(name="bat", projection="full")
1427 parsed = urlparse(request.uri)
1428 q = parse_qs(parsed[4])
1429 self.assertEqual(q["name"], ["bat"])
1430 self.assertEqual(q["projection"], ["full"])
Joe Gregoriof863f7a2011-02-24 03:24:44 -05001431
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001432 def test_nested_resources(self):
1433 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001434 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001435 self.assertTrue(getattr(zoo, "animals"))
1436 request = zoo.my().favorites().list(max_results="5")
1437 parsed = urlparse(request.uri)
1438 q = parse_qs(parsed[4])
1439 self.assertEqual(q["max-results"], ["5"])
Joe Gregorio2379ecc2010-10-26 10:51:28 -04001440
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001441 @unittest.skipIf(six.PY3, "print is not a reserved name in Python 3")
1442 def test_methods_with_reserved_names(self):
1443 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
1444 zoo = build("zoo", "v1", http=self.http)
1445 self.assertTrue(getattr(zoo, "animals"))
1446 request = zoo.global_().print_().assert_(max_results="5")
1447 parsed = urlparse(request.uri)
1448 self.assertEqual(parsed[2], "/zoo/v1/global/print/assert")
Joe Gregorio3fada332011-01-07 17:07:45 -05001449
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001450 def test_top_level_functions(self):
1451 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001452 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001453 self.assertTrue(getattr(zoo, "query"))
1454 request = zoo.query(q="foo")
1455 parsed = urlparse(request.uri)
1456 q = parse_qs(parsed[4])
1457 self.assertEqual(q["q"], ["foo"])
Joe Gregoriod92897c2011-07-07 11:44:56 -04001458
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001459 def test_simple_media_uploads(self):
1460 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001461 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001462 doc = getattr(zoo.animals().insert, "__doc__")
1463 self.assertTrue("media_body" in doc)
Joe Gregorio2379ecc2010-10-26 10:51:28 -04001464
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001465 def test_simple_media_upload_no_max_size_provided(self):
1466 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001467 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001468 request = zoo.animals().crossbreed(media_body=datafile("small.png"))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001469 self.assertEqual("image/png", request.headers["content-type"])
1470 self.assertEqual(b"PNG", request.body[1:4])
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001471
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001472 def test_simple_media_raise_correct_exceptions(self):
1473 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001474 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -04001475
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001476 try:
1477 zoo.animals().insert(media_body=datafile("smiley.png"))
1478 self.fail("should throw exception if media is too large.")
1479 except MediaUploadSizeError:
1480 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001481
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001482 try:
1483 zoo.animals().insert(media_body=datafile("small.jpg"))
1484 self.fail("should throw exception if mimetype is unacceptable.")
1485 except UnacceptableMimeTypeError:
1486 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001487
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001488 def test_simple_media_good_upload(self):
1489 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001490 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001491
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001492 request = zoo.animals().insert(media_body=datafile("small.png"))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001493 self.assertEqual("image/png", request.headers["content-type"])
1494 self.assertEqual(b"PNG", request.body[1:4])
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001495 assertUrisEqual(
1496 self,
1497 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json",
1498 request.uri,
1499 )
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001500
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001501 def test_simple_media_unknown_mimetype(self):
1502 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001503 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001504
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001505 try:
1506 zoo.animals().insert(media_body=datafile("small-png"))
1507 self.fail("should throw exception if mimetype is unknown.")
1508 except UnknownFileType:
1509 pass
Brian J. Watson38051ac2016-10-25 07:53:08 -07001510
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001511 request = zoo.animals().insert(
1512 media_body=datafile("small-png"), media_mime_type="image/png"
1513 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001514 self.assertEqual("image/png", request.headers["content-type"])
1515 self.assertEqual(b"PNG", request.body[1:4])
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001516 assertUrisEqual(
1517 self,
1518 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json",
1519 request.uri,
1520 )
Brian J. Watson38051ac2016-10-25 07:53:08 -07001521
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001522 def test_multipart_media_raise_correct_exceptions(self):
1523 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001524 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Brian J. Watson38051ac2016-10-25 07:53:08 -07001525
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001526 try:
1527 zoo.animals().insert(media_body=datafile("smiley.png"), body={})
1528 self.fail("should throw exception if media is too large.")
1529 except MediaUploadSizeError:
1530 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001531
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001532 try:
1533 zoo.animals().insert(media_body=datafile("small.jpg"), body={})
1534 self.fail("should throw exception if mimetype is unacceptable.")
1535 except UnacceptableMimeTypeError:
1536 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001537
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001538 def test_multipart_media_good_upload(self, static_discovery=False):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001539 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001540 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001541
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001542 request = zoo.animals().insert(media_body=datafile("small.png"), body={})
1543 self.assertTrue(request.headers["content-type"].startswith("multipart/related"))
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001544 contents = read_datafile("small.png", "rb")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001545 boundary = re.match(b"--=+([^=]+)", request.body).group(1)
1546 self.assertEqual(
1547 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
1548 b"--==============="
1549 + boundary
1550 + b"==\n"
1551 + b"Content-Type: application/json\n"
1552 + b"MIME-Version: 1.0\n\n"
1553 + b'{"data": {}}\n'
1554 + b"--==============="
1555 + boundary
1556 + b"==\n"
1557 + b"Content-Type: image/png\n"
1558 + b"MIME-Version: 1.0\n"
1559 + b"Content-Transfer-Encoding: binary\n\n"
1560 + contents
1561 + b"\n--==============="
1562 + boundary
1563 + b"==--",
1564 )
1565 assertUrisEqual(
1566 self,
1567 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json",
1568 request.uri,
1569 )
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001570
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001571 def test_media_capable_method_without_media(self):
1572 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001573 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001574
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001575 request = zoo.animals().insert(body={})
1576 self.assertTrue(request.headers["content-type"], "application/json")
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001577
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001578 def test_resumable_multipart_media_good_upload(self):
1579 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001580 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001581
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001582 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1583 request = zoo.animals().insert(media_body=media_upload, body={})
1584 self.assertTrue(request.headers["content-type"].startswith("application/json"))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001585 self.assertEqual('{"data": {}}', request.body)
1586 self.assertEqual(media_upload, request.resumable)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001587
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001588 self.assertEqual("image/png", request.resumable.mimetype())
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001589
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001590 self.assertNotEqual(request.body, None)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001591 self.assertEqual(request.resumable_uri, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001592
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001593 http = HttpMockSequence(
1594 [
1595 ({"status": "200", "location": "http://upload.example.com"}, ""),
1596 ({"status": "308", "location": "http://upload.example.com/2"}, ""),
1597 (
1598 {
1599 "status": "308",
1600 "location": "http://upload.example.com/3",
1601 "range": "0-12",
1602 },
1603 "",
1604 ),
1605 (
1606 {
1607 "status": "308",
1608 "location": "http://upload.example.com/4",
1609 "range": "0-%d" % (media_upload.size() - 2),
1610 },
1611 "",
1612 ),
1613 ({"status": "200"}, '{"foo": "bar"}'),
1614 ]
1615 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001616
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001617 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001618 self.assertEqual(None, body)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001619 self.assertTrue(isinstance(status, MediaUploadProgress))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001620 self.assertEqual(0, status.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001621
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001622 # Two requests should have been made and the resumable_uri should have been
1623 # updated for each one.
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001624 self.assertEqual(request.resumable_uri, "http://upload.example.com/2")
1625 self.assertEqual(media_upload, request.resumable)
1626 self.assertEqual(0, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001627
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001628 # This next chuck call should upload the first chunk
1629 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001630 self.assertEqual(request.resumable_uri, "http://upload.example.com/3")
1631 self.assertEqual(media_upload, request.resumable)
1632 self.assertEqual(13, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001633
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001634 # This call will upload the next chunk
1635 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001636 self.assertEqual(request.resumable_uri, "http://upload.example.com/4")
1637 self.assertEqual(media_upload.size() - 1, request.resumable_progress)
1638 self.assertEqual('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001639
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001640 # Final call to next_chunk should complete the upload.
1641 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001642 self.assertEqual(body, {"foo": "bar"})
1643 self.assertEqual(status, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001644
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001645 def test_resumable_media_good_upload(self):
1646 """Not a multipart upload."""
1647 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001648 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001649
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001650 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1651 request = zoo.animals().insert(media_body=media_upload, body=None)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001652 self.assertEqual(media_upload, request.resumable)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001653
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001654 self.assertEqual("image/png", request.resumable.mimetype())
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001655
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001656 self.assertEqual(request.body, None)
1657 self.assertEqual(request.resumable_uri, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001658
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001659 http = HttpMockSequence(
1660 [
1661 ({"status": "200", "location": "http://upload.example.com"}, ""),
1662 (
1663 {
1664 "status": "308",
1665 "location": "http://upload.example.com/2",
1666 "range": "0-12",
1667 },
1668 "",
1669 ),
1670 (
1671 {
1672 "status": "308",
1673 "location": "http://upload.example.com/3",
1674 "range": "0-%d" % (media_upload.size() - 2),
1675 },
1676 "",
1677 ),
1678 ({"status": "200"}, '{"foo": "bar"}'),
1679 ]
1680 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001681
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001682 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001683 self.assertEqual(None, body)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001684 self.assertTrue(isinstance(status, MediaUploadProgress))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001685 self.assertEqual(13, status.resumable_progress)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001686
1687 # Two requests should have been made and the resumable_uri should have been
1688 # updated for each one.
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001689 self.assertEqual(request.resumable_uri, "http://upload.example.com/2")
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001690
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001691 self.assertEqual(media_upload, request.resumable)
1692 self.assertEqual(13, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001693
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001694 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001695 self.assertEqual(request.resumable_uri, "http://upload.example.com/3")
1696 self.assertEqual(media_upload.size() - 1, request.resumable_progress)
1697 self.assertEqual(request.body, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001698
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001699 # Final call to next_chunk should complete the upload.
1700 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001701 self.assertEqual(body, {"foo": "bar"})
1702 self.assertEqual(status, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001703
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001704 def test_resumable_media_good_upload_from_execute(self):
1705 """Not a multipart upload."""
1706 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001707 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001708
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001709 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1710 request = zoo.animals().insert(media_body=media_upload, body=None)
1711 assertUrisEqual(
1712 self,
1713 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json",
1714 request.uri,
1715 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001716
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001717 http = HttpMockSequence(
1718 [
1719 ({"status": "200", "location": "http://upload.example.com"}, ""),
1720 (
1721 {
1722 "status": "308",
1723 "location": "http://upload.example.com/2",
1724 "range": "0-12",
1725 },
1726 "",
1727 ),
1728 (
1729 {
1730 "status": "308",
1731 "location": "http://upload.example.com/3",
1732 "range": "0-%d" % media_upload.size(),
1733 },
1734 "",
1735 ),
1736 ({"status": "200"}, '{"foo": "bar"}'),
1737 ]
1738 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001739
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001740 body = request.execute(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001741 self.assertEqual(body, {"foo": "bar"})
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001742
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001743 def test_resumable_media_fail_unknown_response_code_first_request(self):
1744 """Not a multipart upload."""
1745 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001746 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001747
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001748 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1749 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001750
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001751 http = HttpMockSequence(
1752 [({"status": "400", "location": "http://upload.example.com"}, "")]
1753 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001754
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001755 try:
1756 request.execute(http=http)
1757 self.fail("Should have raised ResumableUploadError.")
1758 except ResumableUploadError as e:
1759 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001760
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001761 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
1762 """Not a multipart upload."""
1763 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001764 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001765
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001766 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1767 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001768
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001769 http = HttpMockSequence(
1770 [
1771 ({"status": "200", "location": "http://upload.example.com"}, ""),
1772 ({"status": "400"}, ""),
1773 ]
1774 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001775
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001776 self.assertRaises(HttpError, request.execute, http=http)
1777 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001778
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001779 http = HttpMockSequence(
1780 [
1781 ({"status": "308", "range": "0-5"}, ""),
1782 ({"status": "308", "range": "0-6"}, ""),
1783 ]
1784 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001785
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001786 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001787 self.assertEqual(
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001788 status.resumable_progress,
1789 7,
1790 "Should have first checked length and then tried to PUT more.",
1791 )
1792 self.assertFalse(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001793
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001794 # Put it back in an error state.
1795 http = HttpMockSequence([({"status": "400"}, "")])
1796 self.assertRaises(HttpError, request.execute, http=http)
1797 self.assertTrue(request._in_error_state)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001798
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001799 # Pretend the last request that 400'd actually succeeded.
1800 http = HttpMockSequence([({"status": "200"}, '{"foo": "bar"}')])
1801 status, body = request.next_chunk(http=http)
1802 self.assertEqual(body, {"foo": "bar"})
Joe Gregorio910b9b12012-06-12 09:36:30 -04001803
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001804 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1805 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001806 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001807
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001808 # Set up a seekable stream and try to upload in single chunk.
1809 fd = BytesIO(b'01234"56789"')
1810 media_upload = MediaIoBaseUpload(
1811 fd=fd, mimetype="text/plain", chunksize=-1, resumable=True
1812 )
Joe Gregorio910b9b12012-06-12 09:36:30 -04001813
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001814 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001815
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001816 # The single chunk fails, restart at the right point.
1817 http = HttpMockSequence(
1818 [
1819 ({"status": "200", "location": "http://upload.example.com"}, ""),
1820 (
1821 {
1822 "status": "308",
1823 "location": "http://upload.example.com/2",
1824 "range": "0-4",
1825 },
1826 "",
1827 ),
1828 ({"status": "200"}, "echo_request_body"),
1829 ]
1830 )
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001831
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001832 body = request.execute(http=http)
1833 self.assertEqual("56789", body)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001834
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001835 def test_media_io_base_stream_chunksize_resume(self):
1836 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001837 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001838
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001839 # Set up a seekable stream and try to upload in chunks.
1840 fd = BytesIO(b"0123456789")
1841 media_upload = MediaIoBaseUpload(
1842 fd=fd, mimetype="text/plain", chunksize=5, resumable=True
1843 )
Joe Gregorio5c120db2012-08-23 09:13:55 -04001844
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001845 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001846
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001847 # The single chunk fails, pull the content sent out of the exception.
1848 http = HttpMockSequence(
1849 [
1850 ({"status": "200", "location": "http://upload.example.com"}, ""),
1851 ({"status": "400"}, "echo_request_body"),
1852 ]
1853 )
Pat Ferateed9affd2015-03-03 16:03:15 -08001854
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001855 try:
1856 body = request.execute(http=http)
1857 except HttpError as e:
1858 self.assertEqual(b"01234", e.content)
Pat Ferateed9affd2015-03-03 16:03:15 -08001859
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001860 def test_resumable_media_handle_uploads_of_unknown_size(self):
1861 http = HttpMockSequence(
1862 [
1863 ({"status": "200", "location": "http://upload.example.com"}, ""),
1864 ({"status": "200"}, "echo_request_headers_as_json"),
1865 ]
1866 )
Pat Ferateed9affd2015-03-03 16:03:15 -08001867
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001868 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001869 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001870
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001871 # Create an upload that doesn't know the full size of the media.
1872 class IoBaseUnknownLength(MediaUpload):
1873 def chunksize(self):
1874 return 10
Joe Gregorio910b9b12012-06-12 09:36:30 -04001875
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001876 def mimetype(self):
1877 return "image/png"
Joe Gregorio910b9b12012-06-12 09:36:30 -04001878
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001879 def size(self):
1880 return None
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001881
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001882 def resumable(self):
1883 return True
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001884
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001885 def getbytes(self, begin, length):
1886 return "0123456789"
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001887
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001888 upload = IoBaseUnknownLength()
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001889
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001890 request = zoo.animals().insert(media_body=upload, body=None)
1891 status, body = request.next_chunk(http=http)
1892 self.assertEqual(body, {"Content-Range": "bytes 0-9/*", "Content-Length": "10"})
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001893
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001894 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1895 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001896 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001897
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001898 class IoBaseHasStream(MediaUpload):
1899 def chunksize(self):
1900 return 10
Joe Gregorio44454e42012-06-15 08:38:53 -04001901
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001902 def mimetype(self):
1903 return "image/png"
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001904
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001905 def size(self):
1906 return None
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001907
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001908 def resumable(self):
1909 return True
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001910
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001911 def getbytes(self, begin, length):
1912 return "0123456789"
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001913
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001914 def has_stream(self):
1915 return True
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001916
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001917 def stream(self):
1918 raise NotImplementedError()
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001919
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001920 upload = IoBaseHasStream()
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001921
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001922 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001923
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001924 sys.version_info = (2, 6, 5, "final", 0)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001925
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001926 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001927
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001928 # This should raise an exception because stream() will be called.
1929 http = HttpMockSequence(
1930 [
1931 ({"status": "200", "location": "http://upload.example.com"}, ""),
1932 ({"status": "200"}, "echo_request_headers_as_json"),
1933 ]
1934 )
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001935
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001936 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001937
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001938 sys.version_info = orig_version
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001939
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001940 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1941 http = HttpMockSequence(
1942 [
1943 ({"status": "200", "location": "http://upload.example.com"}, ""),
1944 ({"status": "200"}, "echo_request_headers_as_json"),
1945 ]
1946 )
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001947
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001948 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001949 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001950
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001951 fd = BytesIO(b"data goes here")
Joe Gregorio44454e42012-06-15 08:38:53 -04001952
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001953 # Create an upload that doesn't know the full size of the media.
1954 upload = MediaIoBaseUpload(
1955 fd=fd, mimetype="image/png", chunksize=15, resumable=True
1956 )
Joe Gregorio44454e42012-06-15 08:38:53 -04001957
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001958 request = zoo.animals().insert(media_body=upload, body=None)
1959 status, body = request.next_chunk(http=http)
1960 self.assertEqual(
1961 body, {"Content-Range": "bytes 0-13/14", "Content-Length": "14"}
1962 )
Joe Gregorio44454e42012-06-15 08:38:53 -04001963
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001964 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1965 http = HttpMockSequence(
1966 [
1967 ({"status": "200", "location": "http://upload.example.com"}, ""),
1968 ({"status": "400"}, ""),
1969 ]
1970 )
Joe Gregorio44454e42012-06-15 08:38:53 -04001971
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001972 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001973 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001974
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001975 # Create an upload that doesn't know the full size of the media.
1976 fd = BytesIO(b"data goes here")
Joe Gregorio910b9b12012-06-12 09:36:30 -04001977
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001978 upload = MediaIoBaseUpload(
1979 fd=fd, mimetype="image/png", chunksize=500, resumable=True
1980 )
Joe Gregorio910b9b12012-06-12 09:36:30 -04001981
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001982 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001983
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001984 # Put it in an error state.
1985 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001986
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001987 http = HttpMockSequence(
1988 [({"status": "400", "range": "0-5"}, "echo_request_headers_as_json")]
1989 )
1990 try:
1991 # Should resume the upload by first querying the status of the upload.
1992 request.next_chunk(http=http)
1993 except HttpError as e:
1994 expected = {"Content-Range": "bytes */14", "content-length": "0"}
1995 self.assertEqual(
1996 expected,
1997 json.loads(e.content.decode("utf-8")),
1998 "Should send an empty body when requesting the current upload status.",
1999 )
Joe Gregorio910b9b12012-06-12 09:36:30 -04002000
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002001 def test_pickle(self):
2002 sorted_resource_keys = [
2003 "_baseUrl",
2004 "_developerKey",
2005 "_dynamic_attrs",
2006 "_http",
2007 "_model",
2008 "_requestBuilder",
2009 "_resourceDesc",
2010 "_rootDesc",
2011 "_schema",
2012 "animals",
2013 "global_",
2014 "load",
2015 "loadNoTemplate",
2016 "my",
2017 "new_batch_http_request",
2018 "query",
2019 "scopedAnimals",
2020 ]
Joe Gregorio910b9b12012-06-12 09:36:30 -04002021
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002022 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002023 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002024 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05002025
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002026 pickled_zoo = pickle.dumps(zoo)
2027 new_zoo = pickle.loads(pickled_zoo)
2028 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
2029 self.assertTrue(hasattr(new_zoo, "animals"))
2030 self.assertTrue(callable(new_zoo.animals))
2031 self.assertTrue(hasattr(new_zoo, "global_"))
2032 self.assertTrue(callable(new_zoo.global_))
2033 self.assertTrue(hasattr(new_zoo, "load"))
2034 self.assertTrue(callable(new_zoo.load))
2035 self.assertTrue(hasattr(new_zoo, "loadNoTemplate"))
2036 self.assertTrue(callable(new_zoo.loadNoTemplate))
2037 self.assertTrue(hasattr(new_zoo, "my"))
2038 self.assertTrue(callable(new_zoo.my))
2039 self.assertTrue(hasattr(new_zoo, "query"))
2040 self.assertTrue(callable(new_zoo.query))
2041 self.assertTrue(hasattr(new_zoo, "scopedAnimals"))
2042 self.assertTrue(callable(new_zoo.scopedAnimals))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002043
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002044 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
2045 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
2046 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
2047 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
2048 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
2049 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
2050 # _http, _model and _schema won't be equal since we will get new
2051 # instances upon un-pickling
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002052
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002053 def _dummy_zoo_request(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07002054 zoo_contents = read_datafile("zoo.json")
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002055
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002056 zoo_uri = uritemplate.expand(DISCOVERY_URI, {"api": "zoo", "apiVersion": "v1"})
2057 if "REMOTE_ADDR" in os.environ:
2058 zoo_uri = util._add_query_parameter(
2059 zoo_uri, "userIp", os.environ["REMOTE_ADDR"]
2060 )
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002061
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002062 http = build_http()
2063 original_request = http.request
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002064
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002065 def wrapped_request(uri, method="GET", *args, **kwargs):
2066 if uri == zoo_uri:
2067 return httplib2.Response({"status": "200"}), zoo_contents
2068 return original_request(uri, method=method, *args, **kwargs)
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002069
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002070 http.request = wrapped_request
2071 return http
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002072
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002073 def _dummy_token(self):
2074 access_token = "foo"
2075 client_id = "some_client_id"
2076 client_secret = "cOuDdkfjxxnv+"
2077 refresh_token = "1/0/a.df219fjls0"
2078 token_expiry = datetime.datetime.utcnow()
2079 user_agent = "refresh_checker/1.0"
2080 return OAuth2Credentials(
2081 access_token,
2082 client_id,
2083 client_secret,
2084 refresh_token,
2085 token_expiry,
2086 GOOGLE_TOKEN_URI,
2087 user_agent,
2088 )
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002089
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002090 def test_pickle_with_credentials(self):
2091 credentials = self._dummy_token()
2092 http = self._dummy_zoo_request()
2093 http = credentials.authorize(http)
2094 self.assertTrue(hasattr(http.request, "credentials"))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002095
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002096 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002097 pickled_zoo = pickle.dumps(zoo)
2098 new_zoo = pickle.loads(pickled_zoo)
2099 self.assertEqual(sorted(zoo.__dict__.keys()), sorted(new_zoo.__dict__.keys()))
2100 new_http = new_zoo._http
2101 self.assertFalse(hasattr(new_http.request, "credentials"))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002102
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002103 def test_resumable_media_upload_no_content(self):
2104 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002105 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
andrewnestera4a44cf2017-03-31 16:09:31 +03002106
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002107 media_upload = MediaFileUpload(datafile("empty"), resumable=True)
2108 request = zoo.animals().insert(media_body=media_upload, body=None)
andrewnestera4a44cf2017-03-31 16:09:31 +03002109
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07002110 self.assertEqual(media_upload, request.resumable)
2111 self.assertEqual(request.body, None)
2112 self.assertEqual(request.resumable_uri, None)
andrewnestera4a44cf2017-03-31 16:09:31 +03002113
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002114 http = HttpMockSequence(
2115 [
2116 ({"status": "200", "location": "http://upload.example.com"}, ""),
2117 (
2118 {
2119 "status": "308",
2120 "location": "http://upload.example.com/2",
2121 "range": "0-0",
2122 },
2123 "",
2124 ),
2125 ]
2126 )
andrewnestera4a44cf2017-03-31 16:09:31 +03002127
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002128 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07002129 self.assertEqual(None, body)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002130 self.assertTrue(isinstance(status, MediaUploadProgress))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07002131 self.assertEqual(0, status.progress())
andrewnestera4a44cf2017-03-31 16:09:31 +03002132
Joe Gregorio708388c2012-06-15 13:43:04 -04002133
Joe Gregorioc5c5a372010-09-22 11:42:32 -04002134class Next(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002135 def test_next_successful_none_on_no_next_page_token(self):
2136 self.http = HttpMock(datafile("tasks.json"), {"status": "200"})
2137 tasks = build("tasks", "v1", http=self.http)
2138 request = tasks.tasklists().list()
2139 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
Joe Gregorio00cf1d92010-09-27 09:22:03 -04002140
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002141 def test_next_successful_none_on_empty_page_token(self):
2142 self.http = HttpMock(datafile("tasks.json"), {"status": "200"})
2143 tasks = build("tasks", "v1", http=self.http)
2144 request = tasks.tasklists().list()
2145 next_request = tasks.tasklists().list_next(request, {"nextPageToken": ""})
2146 self.assertEqual(None, next_request)
Joe Gregorio3c676f92011-07-25 10:38:14 -04002147
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002148 def test_next_successful_with_next_page_token(self):
2149 self.http = HttpMock(datafile("tasks.json"), {"status": "200"})
2150 tasks = build("tasks", "v1", http=self.http)
2151 request = tasks.tasklists().list()
2152 next_request = tasks.tasklists().list_next(request, {"nextPageToken": "123abc"})
2153 parsed = list(urlparse(next_request.uri))
2154 q = parse_qs(parsed[4])
2155 self.assertEqual(q["pageToken"][0], "123abc")
Son Dinh2a9a2132015-07-23 16:30:56 +00002156
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002157 def test_next_successful_with_next_page_token_alternate_name(self):
2158 self.http = HttpMock(datafile("bigquery.json"), {"status": "200"})
2159 bigquery = build("bigquery", "v2", http=self.http)
2160 request = bigquery.tabledata().list(datasetId="", projectId="", tableId="")
2161 next_request = bigquery.tabledata().list_next(request, {"pageToken": "123abc"})
2162 parsed = list(urlparse(next_request.uri))
2163 q = parse_qs(parsed[4])
2164 self.assertEqual(q["pageToken"][0], "123abc")
Joe Gregorio3c676f92011-07-25 10:38:14 -04002165
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002166 def test_next_successful_with_next_page_token_in_body(self):
2167 self.http = HttpMock(datafile("logging.json"), {"status": "200"})
2168 logging = build("logging", "v2", http=self.http)
2169 request = logging.entries().list(body={})
2170 next_request = logging.entries().list_next(request, {"nextPageToken": "123abc"})
2171 body = JsonModel().deserialize(next_request.body)
2172 self.assertEqual(body["pageToken"], "123abc")
Thomas Coffee20af04d2017-02-10 15:24:44 -08002173
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002174 def test_next_with_method_with_no_properties(self):
2175 self.http = HttpMock(datafile("latitude.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002176 service = build("latitude", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002177 service.currentLocation().get()
Thomas Coffee20af04d2017-02-10 15:24:44 -08002178
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002179 def test_next_nonexistent_with_no_next_page_token(self):
2180 self.http = HttpMock(datafile("drive.json"), {"status": "200"})
2181 drive = build("drive", "v3", http=self.http)
2182 drive.changes().watch(body={})
2183 self.assertFalse(callable(getattr(drive.changes(), "watch_next", None)))
Thomas Coffee20af04d2017-02-10 15:24:44 -08002184
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002185 def test_next_successful_with_next_page_token_required(self):
2186 self.http = HttpMock(datafile("drive.json"), {"status": "200"})
2187 drive = build("drive", "v3", http=self.http)
2188 request = drive.changes().list(pageToken="startPageToken")
2189 next_request = drive.changes().list_next(request, {"nextPageToken": "123abc"})
2190 parsed = list(urlparse(next_request.uri))
2191 q = parse_qs(parsed[4])
2192 self.assertEqual(q["pageToken"][0], "123abc")
Joe Gregorio00cf1d92010-09-27 09:22:03 -04002193
Joe Gregorioa98733f2011-09-16 10:12:28 -04002194
Joe Gregorio708388c2012-06-15 13:43:04 -04002195class MediaGet(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002196 def test_get_media(self):
2197 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002198 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002199 request = zoo.animals().get_media(name="Lion")
Joe Gregorio708388c2012-06-15 13:43:04 -04002200
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002201 parsed = urlparse(request.uri)
2202 q = parse_qs(parsed[4])
2203 self.assertEqual(q["alt"], ["media"])
2204 self.assertEqual(request.headers["accept"], "*/*")
Joe Gregorio708388c2012-06-15 13:43:04 -04002205
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002206 http = HttpMockSequence([({"status": "200"}, "standing in for media")])
2207 response = request.execute(http=http)
2208 self.assertEqual(b"standing in for media", response)
Joe Gregorio708388c2012-06-15 13:43:04 -04002209
2210
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002211if __name__ == "__main__":
2212 unittest.main()