blob: bdc180bbb8539c3827bf5912f5e7bfee7416af10 [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
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040024
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070025__author__ = "jcgregorio@google.com (Joe Gregorio)"
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040026
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -040027from collections import defaultdict
Daniel Hermesc2113242013-02-27 10:16:13 -080028import copy
Joe Gregoriodc106fc2012-11-20 14:30:14 -050029import datetime
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040030import httplib2
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -040031import io
Craig Citro7ee535d2015-02-23 10:11:14 -080032import itertools
Craig Citro6ae34d72014-08-18 23:10:09 -070033import json
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040034import os
Joe Gregoriodc106fc2012-11-20 14:30:14 -050035import pickle
Phil Ruffwind26178fc2015-10-13 19:00:33 -040036import re
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040037import sys
Tres Seaverf6c1b942021-10-12 12:26:30 -040038import unittest
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -040039import urllib
Joe Gregoriodc106fc2012-11-20 14:30:14 -050040
arithmetic1728981eadf2020-06-02 10:20:10 -070041from parameterized import parameterized
Takashi Matsuo30125122015-08-19 11:42:32 -070042import mock
43
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080044import google.auth.credentials
arithmetic1728981eadf2020-06-02 10:20:10 -070045from google.auth.transport import mtls
46from google.auth.exceptions import MutualTLSChannelError
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080047import google_auth_httplib2
Bu Sun Kim790e7022020-09-11 20:18:06 -060048import google.api_core.exceptions
49
John Asmuth864311d2014-04-24 15:46:08 -040050from googleapiclient.discovery import _fix_up_media_upload
51from googleapiclient.discovery import _fix_up_method_description
52from googleapiclient.discovery import _fix_up_parameters
Craig Citro7ee535d2015-02-23 10:11:14 -080053from googleapiclient.discovery import _urljoin
John Asmuth864311d2014-04-24 15:46:08 -040054from googleapiclient.discovery import build
55from googleapiclient.discovery import build_from_document
56from googleapiclient.discovery import DISCOVERY_URI
57from googleapiclient.discovery import key2param
58from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
Brian J. Watson38051ac2016-10-25 07:53:08 -070059from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE
John Asmuth864311d2014-04-24 15:46:08 -040060from googleapiclient.discovery import ResourceMethodParameters
61from googleapiclient.discovery import STACK_QUERY_PARAMETERS
62from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Dmitry Frenkelf3348f92020-07-15 13:05:58 -070063from googleapiclient.discovery import V1_DISCOVERY_URI
64from googleapiclient.discovery import V2_DISCOVERY_URI
Takashi Matsuo30125122015-08-19 11:42:32 -070065from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE
66from googleapiclient.discovery_cache.base import Cache
John Asmuth864311d2014-04-24 15:46:08 -040067from googleapiclient.errors import HttpError
68from googleapiclient.errors import InvalidJsonError
69from googleapiclient.errors import MediaUploadSizeError
70from googleapiclient.errors import ResumableUploadError
71from googleapiclient.errors import UnacceptableMimeTypeError
Takashi Matsuo3772f9d2015-09-04 12:25:55 -070072from googleapiclient.errors import UnknownApiNameOrVersion
Brian J. Watson38051ac2016-10-25 07:53:08 -070073from googleapiclient.errors import UnknownFileType
Igor Maravić22435292017-01-19 22:28:22 +010074from googleapiclient.http import build_http
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -040075from googleapiclient.http import BatchHttpRequest
John Asmuth864311d2014-04-24 15:46:08 -040076from googleapiclient.http import HttpMock
77from googleapiclient.http import HttpMockSequence
78from googleapiclient.http import MediaFileUpload
79from googleapiclient.http import MediaIoBaseUpload
80from googleapiclient.http import MediaUpload
81from googleapiclient.http import MediaUploadProgress
82from googleapiclient.http import tunnel_patch
Thomas Coffee20af04d2017-02-10 15:24:44 -080083from googleapiclient.model import JsonModel
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +090084from googleapiclient.schema import Schemas
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080085from oauth2client import GOOGLE_TOKEN_URI
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080086from oauth2client.client import OAuth2Credentials, GoogleCredentials
Joe Gregorio79daca02013-03-29 16:25:52 -040087
Bu Sun Kima9583f72021-03-15 09:12:02 -060088
Helen Koikede13e3b2018-04-26 16:05:16 -030089from googleapiclient import _helpers as util
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -070090
Joe Gregoriodc106fc2012-11-20 14:30:14 -050091import uritemplate
92
Joe Gregoriocb8103d2011-02-11 23:20:52 -050093
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070094DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
Joe Gregoriocb8103d2011-02-11 23:20:52 -050095
Joe Gregorioa98733f2011-09-16 10:12:28 -040096
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050097def assertUrisEqual(testcase, expected, actual):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070098 """Test that URIs are the same, up to reordering of query parameters."""
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -040099 expected = urllib.parse.urlparse(expected)
100 actual = urllib.parse.urlparse(actual)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700101 testcase.assertEqual(expected.scheme, actual.scheme)
102 testcase.assertEqual(expected.netloc, actual.netloc)
103 testcase.assertEqual(expected.path, actual.path)
104 testcase.assertEqual(expected.params, actual.params)
105 testcase.assertEqual(expected.fragment, actual.fragment)
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -0400106 expected_query = urllib.parse.parse_qs(expected.query)
107 actual_query = urllib.parse.parse_qs(actual.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700108 for name in list(expected_query.keys()):
109 testcase.assertEqual(expected_query[name], actual_query[name])
110 for name in list(actual_query.keys()):
111 testcase.assertEqual(expected_query[name], actual_query[name])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500112
113
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700114def assert_discovery_uri(testcase, actual, service_name, version, discovery):
115 """Assert that discovery URI used was the one that was expected
116 for a given service and version."""
117 params = {"api": service_name, "apiVersion": version}
118 expanded_requested_uri = uritemplate.expand(discovery, params)
119 assertUrisEqual(testcase, expanded_requested_uri, actual)
120
121
Bu Sun Kim790e7022020-09-11 20:18:06 -0600122def validate_discovery_requests(testcase, http_mock, service_name, version, discovery):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700123 """Validates that there have > 0 calls to Http Discovery
124 and that LAST discovery URI used was the one that was expected
125 for a given service and version."""
126 testcase.assertTrue(len(http_mock.request_sequence) > 0)
127 if len(http_mock.request_sequence) > 0:
128 actual_uri = http_mock.request_sequence[-1][0]
Bu Sun Kim790e7022020-09-11 20:18:06 -0600129 assert_discovery_uri(testcase, actual_uri, service_name, version, discovery)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700130
131
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500132def datafile(filename):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700133 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400134
135
Bu Sun Kim790e7022020-09-11 20:18:06 -0600136def read_datafile(filename, mode="r"):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700137 with open(datafile(filename), mode=mode) as f:
138 return f.read()
139
140
Joe Gregorio504a17f2012-12-07 14:14:26 -0500141class SetupHttplib2(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700142 def test_retries(self):
143 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
144 self.assertEqual(1, httplib2.RETRIES)
Joe Gregorio504a17f2012-12-07 14:14:26 -0500145
146
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400147class Utilities(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700148 def setUp(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700149 self.zoo_root_desc = json.loads(read_datafile("zoo.json", "r"))
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700150 self.zoo_get_method_desc = self.zoo_root_desc["methods"]["query"]
151 self.zoo_animals_resource = self.zoo_root_desc["resources"]["animals"]
152 self.zoo_insert_method_desc = self.zoo_animals_resource["methods"]["insert"]
153 self.zoo_schema = Schemas(self.zoo_root_desc)
Daniel Hermesc2113242013-02-27 10:16:13 -0800154
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700155 def test_key2param(self):
156 self.assertEqual("max_results", key2param("max-results"))
157 self.assertEqual("x007_bond", key2param("007-bond"))
Daniel Hermesc2113242013-02-27 10:16:13 -0800158
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700159 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc, schema):
160 self.assertEqual(method_desc["httpMethod"], http_method)
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400161
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700162 method_desc_copy = copy.deepcopy(method_desc)
163 self.assertEqual(method_desc, method_desc_copy)
Daniel Hermesc2113242013-02-27 10:16:13 -0800164
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700165 parameters = _fix_up_parameters(
166 method_desc_copy, root_desc, http_method, schema
167 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800168
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700169 self.assertNotEqual(method_desc, method_desc_copy)
Daniel Hermesc2113242013-02-27 10:16:13 -0800170
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700171 for param_name in STACK_QUERY_PARAMETERS:
172 self.assertEqual(
173 STACK_QUERY_PARAMETER_DEFAULT_VALUE, parameters[param_name]
174 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800175
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -0400176 for param_name, value in root_desc.get("parameters", {}).items():
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700177 self.assertEqual(value, parameters[param_name])
Daniel Hermesc2113242013-02-27 10:16:13 -0800178
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700179 return parameters
Daniel Hermesc2113242013-02-27 10:16:13 -0800180
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700181 def test_fix_up_parameters_get(self):
182 parameters = self._base_fix_up_parameters_test(
183 self.zoo_get_method_desc, "GET", self.zoo_root_desc, self.zoo_schema
184 )
185 # Since http_method is 'GET'
186 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800187
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700188 def test_fix_up_parameters_insert(self):
189 parameters = self._base_fix_up_parameters_test(
190 self.zoo_insert_method_desc, "POST", self.zoo_root_desc, self.zoo_schema
191 )
192 body = {"description": "The request body.", "type": "object", "$ref": "Animal"}
193 self.assertEqual(parameters["body"], body)
Daniel Hermesc2113242013-02-27 10:16:13 -0800194
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700195 def test_fix_up_parameters_check_body(self):
196 dummy_root_desc = {}
197 dummy_schema = {
198 "Request": {
199 "properties": {
200 "description": "Required. Dummy parameter.",
201 "type": "string",
202 }
203 }
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900204 }
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700205 no_payload_http_method = "DELETE"
206 with_payload_http_method = "PUT"
Daniel Hermesc2113242013-02-27 10:16:13 -0800207
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700208 invalid_method_desc = {"response": "Who cares"}
209 valid_method_desc = {
210 "request": {"key1": "value1", "key2": "value2", "$ref": "Request"}
211 }
Daniel Hermesc2113242013-02-27 10:16:13 -0800212
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700213 parameters = _fix_up_parameters(
214 invalid_method_desc, dummy_root_desc, no_payload_http_method, dummy_schema
215 )
216 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800217
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700218 parameters = _fix_up_parameters(
219 valid_method_desc, dummy_root_desc, no_payload_http_method, dummy_schema
220 )
221 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800222
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700223 parameters = _fix_up_parameters(
224 invalid_method_desc, dummy_root_desc, with_payload_http_method, dummy_schema
225 )
226 self.assertFalse("body" in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800227
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700228 parameters = _fix_up_parameters(
229 valid_method_desc, dummy_root_desc, with_payload_http_method, dummy_schema
230 )
231 body = {
232 "description": "The request body.",
233 "type": "object",
234 "$ref": "Request",
235 "key1": "value1",
236 "key2": "value2",
237 }
238 self.assertEqual(parameters["body"], body)
Daniel Hermesc2113242013-02-27 10:16:13 -0800239
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700240 def test_fix_up_parameters_optional_body(self):
241 # Request with no parameters
242 dummy_schema = {"Request": {"properties": {}}}
243 method_desc = {"request": {"$ref": "Request"}}
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900244
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700245 parameters = _fix_up_parameters(method_desc, {}, "POST", dummy_schema)
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900246
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700247 def _base_fix_up_method_description_test(
248 self,
249 method_desc,
250 initial_parameters,
251 final_parameters,
252 final_accept,
253 final_max_size,
254 final_media_path_url,
255 ):
arithmetic1728981eadf2020-06-02 10:20:10 -0700256 fake_root_desc = {
257 "rootUrl": "http://root/",
258 "servicePath": "fake/",
259 "mtlsRootUrl": "http://root/",
260 }
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700261 fake_path_url = "fake-path/"
Daniel Hermesc2113242013-02-27 10:16:13 -0800262
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700263 accept, max_size, media_path_url = _fix_up_media_upload(
264 method_desc, fake_root_desc, fake_path_url, initial_parameters
265 )
266 self.assertEqual(accept, final_accept)
267 self.assertEqual(max_size, final_max_size)
268 self.assertEqual(media_path_url, final_media_path_url)
269 self.assertEqual(initial_parameters, final_parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800270
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700271 def test_fix_up_media_upload_no_initial_invalid(self):
272 invalid_method_desc = {"response": "Who cares"}
273 self._base_fix_up_method_description_test(
274 invalid_method_desc, {}, {}, [], 0, None
275 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800276
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700277 def test_fix_up_media_upload_no_initial_valid_minimal(self):
278 valid_method_desc = {"mediaUpload": {"accept": []}}
279 final_parameters = {
280 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
281 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
282 }
283 self._base_fix_up_method_description_test(
284 valid_method_desc,
285 {},
286 final_parameters,
287 [],
288 0,
289 "http://root/upload/fake/fake-path/",
290 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800291
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700292 def test_fix_up_media_upload_no_initial_valid_full(self):
293 valid_method_desc = {"mediaUpload": {"accept": ["*/*"], "maxSize": "10GB"}}
294 final_parameters = {
295 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
296 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
297 }
298 ten_gb = 10 * 2 ** 30
299 self._base_fix_up_method_description_test(
300 valid_method_desc,
301 {},
302 final_parameters,
303 ["*/*"],
304 ten_gb,
305 "http://root/upload/fake/fake-path/",
306 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800307
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700308 def test_fix_up_media_upload_with_initial_invalid(self):
309 invalid_method_desc = {"response": "Who cares"}
310 initial_parameters = {"body": {}}
311 self._base_fix_up_method_description_test(
312 invalid_method_desc, initial_parameters, initial_parameters, [], 0, None
313 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800314
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700315 def test_fix_up_media_upload_with_initial_valid_minimal(self):
316 valid_method_desc = {"mediaUpload": {"accept": []}}
317 initial_parameters = {"body": {}}
318 final_parameters = {
319 "body": {},
320 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
321 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
322 }
323 self._base_fix_up_method_description_test(
324 valid_method_desc,
325 initial_parameters,
326 final_parameters,
327 [],
328 0,
329 "http://root/upload/fake/fake-path/",
330 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800331
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700332 def test_fix_up_media_upload_with_initial_valid_full(self):
333 valid_method_desc = {"mediaUpload": {"accept": ["*/*"], "maxSize": "10GB"}}
334 initial_parameters = {"body": {}}
335 final_parameters = {
336 "body": {},
337 "media_body": MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
338 "media_mime_type": MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE,
339 }
340 ten_gb = 10 * 2 ** 30
341 self._base_fix_up_method_description_test(
342 valid_method_desc,
343 initial_parameters,
344 final_parameters,
345 ["*/*"],
346 ten_gb,
347 "http://root/upload/fake/fake-path/",
348 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800349
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700350 def test_fix_up_method_description_get(self):
351 result = _fix_up_method_description(
352 self.zoo_get_method_desc, self.zoo_root_desc, self.zoo_schema
353 )
354 path_url = "query"
355 http_method = "GET"
356 method_id = "bigquery.query"
357 accept = []
358 max_size = 0
359 media_path_url = None
360 self.assertEqual(
361 result, (path_url, http_method, method_id, accept, max_size, media_path_url)
362 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800363
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700364 def test_fix_up_method_description_insert(self):
365 result = _fix_up_method_description(
366 self.zoo_insert_method_desc, self.zoo_root_desc, self.zoo_schema
367 )
368 path_url = "animals"
369 http_method = "POST"
370 method_id = "zoo.animals.insert"
371 accept = ["image/png"]
372 max_size = 1024
373 media_path_url = "https://www.googleapis.com/upload/zoo/v1/animals"
374 self.assertEqual(
375 result, (path_url, http_method, method_id, accept, max_size, media_path_url)
376 )
Daniel Hermesc2113242013-02-27 10:16:13 -0800377
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700378 def test_urljoin(self):
379 # We want to exhaustively test various URL combinations.
380 simple_bases = ["https://www.googleapis.com", "https://www.googleapis.com/"]
381 long_urls = ["foo/v1/bar:custom?alt=json", "/foo/v1/bar:custom?alt=json"]
Craig Citro7ee535d2015-02-23 10:11:14 -0800382
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700383 long_bases = [
384 "https://www.googleapis.com/foo/v1",
385 "https://www.googleapis.com/foo/v1/",
386 ]
387 simple_urls = ["bar:custom?alt=json", "/bar:custom?alt=json"]
Craig Citro7ee535d2015-02-23 10:11:14 -0800388
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700389 final_url = "https://www.googleapis.com/foo/v1/bar:custom?alt=json"
390 for base, url in itertools.product(simple_bases, long_urls):
391 self.assertEqual(final_url, _urljoin(base, url))
392 for base, url in itertools.product(long_bases, simple_urls):
393 self.assertEqual(final_url, _urljoin(base, url))
Craig Citro7ee535d2015-02-23 10:11:14 -0800394
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700395 def test_ResourceMethodParameters_zoo_get(self):
396 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
Craig Citro7ee535d2015-02-23 10:11:14 -0800397
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700398 param_types = {
399 "a": "any",
400 "b": "boolean",
401 "e": "string",
402 "er": "string",
403 "i": "integer",
404 "n": "number",
405 "o": "object",
406 "q": "string",
407 "rr": "string",
408 }
409 keys = list(param_types.keys())
410 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
411 self.assertEqual(parameters.required_params, [])
412 self.assertEqual(sorted(parameters.repeated_params), ["er", "rr"])
413 self.assertEqual(parameters.pattern_params, {"rr": "[a-z]+"})
414 self.assertEqual(
415 sorted(parameters.query_params),
416 ["a", "b", "e", "er", "i", "n", "o", "q", "rr"],
417 )
418 self.assertEqual(parameters.path_params, set())
419 self.assertEqual(parameters.param_types, param_types)
420 enum_params = {"e": ["foo", "bar"], "er": ["one", "two", "three"]}
421 self.assertEqual(parameters.enum_params, enum_params)
Daniel Hermes954e1242013-02-28 09:28:37 -0800422
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700423 def test_ResourceMethodParameters_zoo_animals_patch(self):
424 method_desc = self.zoo_animals_resource["methods"]["patch"]
425 parameters = ResourceMethodParameters(method_desc)
Daniel Hermes954e1242013-02-28 09:28:37 -0800426
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700427 param_types = {"name": "string"}
428 keys = list(param_types.keys())
429 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
430 self.assertEqual(parameters.required_params, ["name"])
431 self.assertEqual(parameters.repeated_params, [])
432 self.assertEqual(parameters.pattern_params, {})
433 self.assertEqual(parameters.query_params, [])
434 self.assertEqual(parameters.path_params, set(["name"]))
435 self.assertEqual(parameters.param_types, param_types)
436 self.assertEqual(parameters.enum_params, {})
Daniel Hermes954e1242013-02-28 09:28:37 -0800437
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400438
Bu Sun Kim98888da2020-09-23 11:10:39 -0600439class Discovery(unittest.TestCase):
440 def test_discovery_http_is_closed(self):
441 http = HttpMock(datafile("malformed.json"), {"status": "200"})
442 service = build("plus", "v1", credentials=mock.sentinel.credentials)
443 http.close.assert_called_once()
444
445
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500446class DiscoveryErrors(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700447 def test_tests_should_be_run_with_strict_positional_enforcement(self):
448 try:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500449 plus = build("plus", "v1", None, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700450 self.fail("should have raised a TypeError exception over missing http=.")
451 except TypeError:
452 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500453
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700454 def test_failed_to_parse_discovery_json(self):
455 self.http = HttpMock(datafile("malformed.json"), {"status": "200"})
456 try:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500457 plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700458 self.fail("should have raised an exception over malformed JSON.")
459 except InvalidJsonError:
460 pass
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400461
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700462 def test_unknown_api_name_or_version(self):
463 http = HttpMockSequence(
464 [
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700465 ({"status": "404"}, read_datafile("zoo.json", "rb")),
466 ({"status": "404"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700467 ]
468 )
469 with self.assertRaises(UnknownApiNameOrVersion):
470 plus = build("plus", "v1", http=http, cache_discovery=False)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500471
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700472 def test_credentials_and_http_mutually_exclusive(self):
473 http = HttpMock(datafile("plus.json"), {"status": "200"})
474 with self.assertRaises(ValueError):
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500475 build("plus", "v1", http=http, credentials=mock.sentinel.credentials, static_discovery=False)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800476
Bu Sun Kim790e7022020-09-11 20:18:06 -0600477 def test_credentials_file_and_http_mutually_exclusive(self):
478 http = HttpMock(datafile("plus.json"), {"status": "200"})
479 with self.assertRaises(ValueError):
480 build(
481 "plus",
482 "v1",
483 http=http,
484 client_options=google.api_core.client_options.ClientOptions(
485 credentials_file="credentials.json"
486 ),
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500487 static_discovery=False,
Bu Sun Kim790e7022020-09-11 20:18:06 -0600488 )
489
490 def test_credentials_and_credentials_file_mutually_exclusive(self):
491 with self.assertRaises(google.api_core.exceptions.DuplicateCredentialArgs):
492 build(
493 "plus",
494 "v1",
495 credentials=mock.sentinel.credentials,
496 client_options=google.api_core.client_options.ClientOptions(
497 credentials_file="credentials.json"
498 ),
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500499 static_discovery=False,
Bu Sun Kim790e7022020-09-11 20:18:06 -0600500 )
501
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500502
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100503class DiscoveryFromDocument(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700504 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400505
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700506 def test_can_build_from_local_document(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700507 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700508 plus = build_from_document(
509 discovery,
510 base="https://www.googleapis.com/",
511 credentials=self.MOCK_CREDENTIALS,
512 )
arithmetic1728981eadf2020-06-02 10:20:10 -0700513 self.assertIsNotNone(plus)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700514 self.assertTrue(hasattr(plus, "activities"))
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500515
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700516 def test_can_build_from_local_deserialized_document(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700517 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700518 discovery = json.loads(discovery)
519 plus = build_from_document(
520 discovery,
521 base="https://www.googleapis.com/",
522 credentials=self.MOCK_CREDENTIALS,
523 )
arithmetic1728981eadf2020-06-02 10:20:10 -0700524 self.assertIsNotNone(plus)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700525 self.assertTrue(hasattr(plus, "activities"))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400526
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700527 def test_building_with_base_remembers_base(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700528 discovery = read_datafile("plus.json")
Joe Gregorioa98733f2011-09-16 10:12:28 -0400529
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700530 base = "https://www.example.com/"
531 plus = build_from_document(
532 discovery, base=base, credentials=self.MOCK_CREDENTIALS
533 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700534 self.assertEqual("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100535
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700536 def test_building_with_optional_http_with_authorization(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700537 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700538 plus = build_from_document(
539 discovery,
540 base="https://www.googleapis.com/",
541 credentials=self.MOCK_CREDENTIALS,
542 )
Igor Maravić22435292017-01-19 22:28:22 +0100543
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700544 # plus service requires Authorization, hence we expect to see AuthorizedHttp object here
545 self.assertIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
546 self.assertIsInstance(plus._http.http, httplib2.Http)
547 self.assertIsInstance(plus._http.http.timeout, int)
548 self.assertGreater(plus._http.http.timeout, 0)
Igor Maravić22435292017-01-19 22:28:22 +0100549
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700550 def test_building_with_optional_http_with_no_authorization(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700551 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700552 # Cleanup auth field, so we would use plain http client
553 discovery = json.loads(discovery)
554 discovery["auth"] = {}
555 discovery = json.dumps(discovery)
Igor Maravić22435292017-01-19 22:28:22 +0100556
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700557 plus = build_from_document(
558 discovery, base="https://www.googleapis.com/", credentials=None
559 )
560 # plus service requires Authorization
561 self.assertIsInstance(plus._http, httplib2.Http)
562 self.assertIsInstance(plus._http.timeout, int)
563 self.assertGreater(plus._http.timeout, 0)
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700564
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700565 def test_building_with_explicit_http(self):
566 http = HttpMock()
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700567 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700568 plus = build_from_document(
569 discovery, base="https://www.googleapis.com/", http=http
570 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700571 self.assertEqual(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100572
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700573 def test_building_with_developer_key_skips_adc(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700574 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700575 plus = build_from_document(
576 discovery, base="https://www.googleapis.com/", developerKey="123"
577 )
578 self.assertIsInstance(plus._http, httplib2.Http)
579 # It should not be an AuthorizedHttp, because that would indicate that
580 # application default credentials were used.
581 self.assertNotIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
Jon Wayne Parrott068eb352017-02-08 10:13:06 -0800582
Bu Sun Kim98888da2020-09-23 11:10:39 -0600583 def test_building_with_context_manager(self):
584 discovery = read_datafile("plus.json")
585 with mock.patch("httplib2.Http") as http:
586 with build_from_document(discovery, base="https://www.googleapis.com/", credentials=self.MOCK_CREDENTIALS) as plus:
587 self.assertIsNotNone(plus)
588 self.assertTrue(hasattr(plus, "activities"))
589 plus._http.http.close.assert_called_once()
590
591 def test_resource_close(self):
592 discovery = read_datafile("plus.json")
Anthonios Partheniou3b4f2e22021-03-19 11:36:01 -0400593
Bu Sun Kima9583f72021-03-15 09:12:02 -0600594 with mock.patch("httplib2.Http", autospec=True) as httplib2_http:
595 http = httplib2_http()
596 plus = build_from_document(
597 discovery,
598 base="https://www.googleapis.com/",
599 http=http,
600 )
601 plus.close()
602 http.close.assert_called_once()
603
604 def test_resource_close_authorized_http(self):
605 discovery = read_datafile("plus.json")
606 with mock.patch("google_auth_httplib2.AuthorizedHttp", autospec=True):
Bu Sun Kim98888da2020-09-23 11:10:39 -0600607 plus = build_from_document(
608 discovery,
609 base="https://www.googleapis.com/",
610 credentials=self.MOCK_CREDENTIALS,
611 )
612 plus.close()
Bu Sun Kima9583f72021-03-15 09:12:02 -0600613 plus._http.close.assert_called_once()
Bu Sun Kim98888da2020-09-23 11:10:39 -0600614
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700615 def test_api_endpoint_override_from_client_options(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700616 discovery = read_datafile("plus.json")
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700617 api_endpoint = "https://foo.googleapis.com/"
618 options = google.api_core.client_options.ClientOptions(
619 api_endpoint=api_endpoint
620 )
Bu Sun Kim8ed1dcd2020-05-14 00:42:22 -0700621 plus = build_from_document(
arithmetic1728981eadf2020-06-02 10:20:10 -0700622 discovery, client_options=options, credentials=self.MOCK_CREDENTIALS
Bu Sun Kim8ed1dcd2020-05-14 00:42:22 -0700623 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -0700624
625 self.assertEqual(plus._baseUrl, api_endpoint)
626
Pavel Kiselev21af37b2020-06-18 19:50:03 +0300627 def test_api_endpoint_override_from_client_options_mapping_object(self):
628
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700629 discovery = read_datafile("plus.json")
Pavel Kiselev21af37b2020-06-18 19:50:03 +0300630 api_endpoint = "https://foo.googleapis.com/"
631 mapping_object = defaultdict(str)
Bu Sun Kim790e7022020-09-11 20:18:06 -0600632 mapping_object["api_endpoint"] = api_endpoint
Bu Sun Kimd500e832021-04-27 16:13:05 -0600633 plus = build_from_document(
634 discovery, client_options=mapping_object, credentials=self.MOCK_CREDENTIALS
635 )
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
arithmetic17281fb3c8e2021-10-07 15:08:32 -0700688 def test_self_signed_jwt_enabled(self):
689 service_account_file_path = os.path.join(DATA_DIR, "service_account.json")
690 creds = google.oauth2.service_account.Credentials.from_service_account_file(service_account_file_path)
691
692 discovery = read_datafile("logging.json")
693
694 with mock.patch("google.oauth2.service_account.Credentials._create_self_signed_jwt") as _create_self_signed_jwt:
695 build_from_document(
696 discovery,
697 credentials=creds,
arithmetic1728623a71e2021-10-12 13:08:17 -0700698 always_use_jwt_access=True,
arithmetic17281fb3c8e2021-10-07 15:08:32 -0700699 )
700 _create_self_signed_jwt.assert_called_with("https://logging.googleapis.com/")
701
702 def test_self_signed_jwt_disabled(self):
703 service_account_file_path = os.path.join(DATA_DIR, "service_account.json")
704 creds = google.oauth2.service_account.Credentials.from_service_account_file(service_account_file_path)
705
706 discovery = read_datafile("logging.json")
707
708 with mock.patch("google.oauth2.service_account.Credentials._create_self_signed_jwt") as _create_self_signed_jwt:
709 build_from_document(
710 discovery,
711 credentials=creds,
arithmetic17281fb3c8e2021-10-07 15:08:32 -0700712 )
713 _create_self_signed_jwt.assert_not_called()
714
Jon Wayne Parrott068eb352017-02-08 10:13:06 -0800715
arithmetic1728981eadf2020-06-02 10:20:10 -0700716REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/"
717MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/"
718
719
720class DiscoveryFromDocumentMutualTLS(unittest.TestCase):
721 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
722 ADC_CERT_PATH = "adc_cert_path"
723 ADC_KEY_PATH = "adc_key_path"
724 ADC_PASSPHRASE = "adc_passphrase"
725
arithmetic17282fc5ca12020-08-27 14:08:12 -0700726 def check_http_client_cert(self, resource, has_client_cert="false"):
arithmetic1728981eadf2020-06-02 10:20:10 -0700727 if isinstance(resource._http, google_auth_httplib2.AuthorizedHttp):
728 certs = list(resource._http.http.certificates.iter(""))
729 else:
730 certs = list(resource._http.certificates.iter(""))
arithmetic17282fc5ca12020-08-27 14:08:12 -0700731 if has_client_cert == "true":
arithmetic1728981eadf2020-06-02 10:20:10 -0700732 self.assertEqual(len(certs), 1)
733 self.assertEqual(
734 certs[0], (self.ADC_KEY_PATH, self.ADC_CERT_PATH, self.ADC_PASSPHRASE)
735 )
736 else:
737 self.assertEqual(len(certs), 0)
738
739 def client_encrypted_cert_source(self):
740 return self.ADC_CERT_PATH, self.ADC_KEY_PATH, self.ADC_PASSPHRASE
741
arithmetic17282fc5ca12020-08-27 14:08:12 -0700742 @parameterized.expand(
743 [
744 ("never", "true"),
745 ("auto", "true"),
746 ("always", "true"),
747 ("never", "false"),
748 ("auto", "false"),
749 ("always", "false"),
750 ]
751 )
752 def test_mtls_not_trigger_if_http_provided(self, use_mtls_env, use_client_cert):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700753 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700754
arithmetic17282fc5ca12020-08-27 14:08:12 -0700755 with mock.patch.dict(
756 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
757 ):
758 with mock.patch.dict(
759 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
760 ):
761 plus = build_from_document(discovery, http=httplib2.Http())
762 self.assertIsNotNone(plus)
763 self.assertEqual(plus._baseUrl, REGULAR_ENDPOINT)
764 self.check_http_client_cert(plus, has_client_cert="false")
arithmetic1728981eadf2020-06-02 10:20:10 -0700765
766 @parameterized.expand(
767 [
arithmetic17282fc5ca12020-08-27 14:08:12 -0700768 ("never", "true"),
769 ("auto", "true"),
770 ("always", "true"),
771 ("never", "false"),
772 ("auto", "false"),
773 ("always", "false"),
arithmetic1728981eadf2020-06-02 10:20:10 -0700774 ]
775 )
arithmetic17282fc5ca12020-08-27 14:08:12 -0700776 def test_exception_with_client_cert_source(self, use_mtls_env, use_client_cert):
777 discovery = read_datafile("plus.json")
778 with mock.patch.dict(
779 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
780 ):
781 with mock.patch.dict(
782 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
783 ):
784 with self.assertRaises(MutualTLSChannelError):
785 build_from_document(
786 discovery,
787 credentials=self.MOCK_CREDENTIALS,
788 client_options={"client_cert_source": mock.Mock()},
789 )
790
791 @parameterized.expand(
792 [
793 ("never", "true", REGULAR_ENDPOINT),
794 ("auto", "true", MTLS_ENDPOINT),
795 ("always", "true", MTLS_ENDPOINT),
796 ("never", "false", REGULAR_ENDPOINT),
797 ("auto", "false", REGULAR_ENDPOINT),
798 ("always", "false", MTLS_ENDPOINT),
799 ]
800 )
801 def test_mtls_with_provided_client_cert(
802 self, use_mtls_env, use_client_cert, base_url
803 ):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700804 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700805
arithmetic17282fc5ca12020-08-27 14:08:12 -0700806 with mock.patch.dict(
807 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
808 ):
809 with mock.patch.dict(
810 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
811 ):
812 plus = build_from_document(
813 discovery,
814 credentials=self.MOCK_CREDENTIALS,
815 client_options={
816 "client_encrypted_cert_source": self.client_encrypted_cert_source
817 },
818 )
819 self.assertIsNotNone(plus)
820 self.check_http_client_cert(plus, has_client_cert=use_client_cert)
821 self.assertEqual(plus._baseUrl, base_url)
arithmetic1728981eadf2020-06-02 10:20:10 -0700822
arithmetic17282fc5ca12020-08-27 14:08:12 -0700823 @parameterized.expand(
824 [
825 ("never", "true"),
826 ("auto", "true"),
827 ("always", "true"),
828 ("never", "false"),
829 ("auto", "false"),
830 ("always", "false"),
831 ]
832 )
833 def test_endpoint_not_switch(self, use_mtls_env, use_client_cert):
arithmetic1728981eadf2020-06-02 10:20:10 -0700834 # Test endpoint is not switched if user provided api endpoint
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700835 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700836
arithmetic17282fc5ca12020-08-27 14:08:12 -0700837 with mock.patch.dict(
838 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
839 ):
840 with mock.patch.dict(
841 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
842 ):
843 plus = build_from_document(
844 discovery,
845 credentials=self.MOCK_CREDENTIALS,
846 client_options={
847 "api_endpoint": "https://foo.googleapis.com",
848 "client_encrypted_cert_source": self.client_encrypted_cert_source,
849 },
850 )
851 self.assertIsNotNone(plus)
852 self.check_http_client_cert(plus, has_client_cert=use_client_cert)
853 self.assertEqual(plus._baseUrl, "https://foo.googleapis.com")
arithmetic1728981eadf2020-06-02 10:20:10 -0700854
855 @parameterized.expand(
856 [
arithmetic17282fc5ca12020-08-27 14:08:12 -0700857 ("never", "true", REGULAR_ENDPOINT),
858 ("auto", "true", MTLS_ENDPOINT),
859 ("always", "true", MTLS_ENDPOINT),
860 ("never", "false", REGULAR_ENDPOINT),
861 ("auto", "false", REGULAR_ENDPOINT),
862 ("always", "false", MTLS_ENDPOINT),
arithmetic1728981eadf2020-06-02 10:20:10 -0700863 ]
864 )
865 @mock.patch(
866 "google.auth.transport.mtls.has_default_client_cert_source", autospec=True
867 )
868 @mock.patch(
869 "google.auth.transport.mtls.default_client_encrypted_cert_source", autospec=True
870 )
871 def test_mtls_with_default_client_cert(
872 self,
873 use_mtls_env,
arithmetic17282fc5ca12020-08-27 14:08:12 -0700874 use_client_cert,
arithmetic1728981eadf2020-06-02 10:20:10 -0700875 base_url,
876 default_client_encrypted_cert_source,
877 has_default_client_cert_source,
878 ):
879 has_default_client_cert_source.return_value = True
880 default_client_encrypted_cert_source.return_value = (
881 self.client_encrypted_cert_source
882 )
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700883 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700884
arithmetic17282fc5ca12020-08-27 14:08:12 -0700885 with mock.patch.dict(
886 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
887 ):
888 with mock.patch.dict(
889 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
890 ):
891 plus = build_from_document(
892 discovery,
893 credentials=self.MOCK_CREDENTIALS,
894 adc_cert_path=self.ADC_CERT_PATH,
895 adc_key_path=self.ADC_KEY_PATH,
896 )
897 self.assertIsNotNone(plus)
898 self.check_http_client_cert(plus, has_client_cert=use_client_cert)
899 self.assertEqual(plus._baseUrl, base_url)
arithmetic1728981eadf2020-06-02 10:20:10 -0700900
901 @parameterized.expand(
902 [
arithmetic17282fc5ca12020-08-27 14:08:12 -0700903 ("never", "true", REGULAR_ENDPOINT),
904 ("auto", "true", REGULAR_ENDPOINT),
905 ("always", "true", MTLS_ENDPOINT),
906 ("never", "false", REGULAR_ENDPOINT),
907 ("auto", "false", REGULAR_ENDPOINT),
908 ("always", "false", MTLS_ENDPOINT),
arithmetic1728981eadf2020-06-02 10:20:10 -0700909 ]
910 )
911 @mock.patch(
912 "google.auth.transport.mtls.has_default_client_cert_source", autospec=True
913 )
914 def test_mtls_with_no_client_cert(
arithmetic17282fc5ca12020-08-27 14:08:12 -0700915 self, use_mtls_env, use_client_cert, base_url, has_default_client_cert_source
arithmetic1728981eadf2020-06-02 10:20:10 -0700916 ):
917 has_default_client_cert_source.return_value = False
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700918 discovery = read_datafile("plus.json")
arithmetic1728981eadf2020-06-02 10:20:10 -0700919
arithmetic17282fc5ca12020-08-27 14:08:12 -0700920 with mock.patch.dict(
921 "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
922 ):
923 with mock.patch.dict(
924 "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
925 ):
926 plus = build_from_document(
927 discovery,
928 credentials=self.MOCK_CREDENTIALS,
929 adc_cert_path=self.ADC_CERT_PATH,
930 adc_key_path=self.ADC_KEY_PATH,
931 )
932 self.assertIsNotNone(plus)
933 self.check_http_client_cert(plus, has_client_cert="false")
934 self.assertEqual(plus._baseUrl, base_url)
arithmetic1728981eadf2020-06-02 10:20:10 -0700935
936
Joe Gregorioa98733f2011-09-16 10:12:28 -0400937class DiscoveryFromHttp(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700938 def setUp(self):
939 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400940
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700941 def tearDown(self):
942 os.environ = self.old_environ
Joe Gregorio583d9e42011-09-16 15:54:15 -0400943
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700944 def test_userip_is_added_to_discovery_uri(self):
945 # build() will raise an HttpError on a 400, use this to pick the request uri
946 # out of the raised exception.
947 os.environ["REMOTE_ADDR"] = "10.0.0.1"
948 try:
949 http = HttpMockSequence(
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700950 [({"status": "400"}, read_datafile("zoo.json", "rb"))]
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700951 )
952 zoo = build(
953 "zoo",
954 "v1",
955 http=http,
956 developerKey=None,
Anthonios Partheniou3b4f2e22021-03-19 11:36:01 -0400957 discoveryServiceUrl="http://example.com"
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700958 )
959 self.fail("Should have raised an exception.")
960 except HttpError as e:
961 self.assertEqual(e.uri, "http://example.com?userIp=10.0.0.1")
Joe Gregorioa98733f2011-09-16 10:12:28 -0400962
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700963 def test_userip_missing_is_not_added_to_discovery_uri(self):
964 # build() will raise an HttpError on a 400, use this to pick the request uri
965 # out of the raised exception.
966 try:
967 http = HttpMockSequence(
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700968 [({"status": "400"}, read_datafile("zoo.json", "rb"))]
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700969 )
970 zoo = build(
971 "zoo",
972 "v1",
973 http=http,
974 developerKey=None,
Anthonios Partheniou3b4f2e22021-03-19 11:36:01 -0400975 discoveryServiceUrl="http://example.com"
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700976 )
977 self.fail("Should have raised an exception.")
978 except HttpError as e:
979 self.assertEqual(e.uri, "http://example.com")
Joe Gregorioa98733f2011-09-16 10:12:28 -0400980
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700981 def test_key_is_added_to_discovery_uri(self):
982 # build() will raise an HttpError on a 400, use this to pick the request uri
983 # out of the raised exception.
984 try:
985 http = HttpMockSequence(
Dmitry Frenkelf3348f92020-07-15 13:05:58 -0700986 [({"status": "400"}, read_datafile("zoo.json", "rb"))]
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700987 )
988 zoo = build(
989 "zoo",
990 "v1",
991 http=http,
992 developerKey="foo",
993 discoveryServiceUrl="http://example.com",
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500994 static_discovery=False,
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700995 )
996 self.fail("Should have raised an exception.")
997 except HttpError as e:
998 self.assertEqual(e.uri, "http://example.com?key=foo")
Arunpn9d779cc2018-11-30 10:25:01 -0800999
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001000 def test_discovery_loading_from_v2_discovery_uri(self):
1001 http = HttpMockSequence(
1002 [
1003 ({"status": "404"}, "Not found"),
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001004 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001005 ]
1006 )
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001007 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001008 self.assertTrue(hasattr(zoo, "animals"))
1009
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001010 def test_api_endpoint_override_from_client_options(self):
1011 http = HttpMockSequence(
1012 [
1013 ({"status": "404"}, "Not found"),
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001014 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001015 ]
1016 )
1017 api_endpoint = "https://foo.googleapis.com/"
1018 options = google.api_core.client_options.ClientOptions(
1019 api_endpoint=api_endpoint
1020 )
1021 zoo = build(
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001022 "zoo", "v1", http=http, cache_discovery=False, client_options=options, static_discovery=False
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001023 )
1024 self.assertEqual(zoo._baseUrl, api_endpoint)
1025
1026 def test_api_endpoint_override_from_client_options_dict(self):
1027 http = HttpMockSequence(
1028 [
1029 ({"status": "404"}, "Not found"),
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001030 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001031 ]
1032 )
1033 api_endpoint = "https://foo.googleapis.com/"
1034 zoo = build(
1035 "zoo",
1036 "v1",
1037 http=http,
1038 cache_discovery=False,
1039 client_options={"api_endpoint": api_endpoint},
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001040 static_discovery=False,
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001041 )
1042 self.assertEqual(zoo._baseUrl, api_endpoint)
1043
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001044 def test_discovery_with_empty_version_uses_v2(self):
Bu Sun Kim790e7022020-09-11 20:18:06 -06001045 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),])
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001046 build("zoo", version=None, http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001047 validate_discovery_requests(self, http, "zoo", None, V2_DISCOVERY_URI)
1048
1049 def test_discovery_with_empty_version_preserves_custom_uri(self):
Bu Sun Kim790e7022020-09-11 20:18:06 -06001050 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),])
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001051 custom_discovery_uri = "https://foo.bar/$discovery"
1052 build(
Bu Sun Kim790e7022020-09-11 20:18:06 -06001053 "zoo",
1054 version=None,
1055 http=http,
1056 cache_discovery=False,
1057 discoveryServiceUrl=custom_discovery_uri,
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001058 static_discovery=False,
Bu Sun Kim790e7022020-09-11 20:18:06 -06001059 )
1060 validate_discovery_requests(self, http, "zoo", None, custom_discovery_uri)
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001061
1062 def test_discovery_with_valid_version_uses_v1(self):
Bu Sun Kim790e7022020-09-11 20:18:06 -06001063 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),])
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001064 build("zoo", version="v123", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelcd4e8f42020-07-30 12:34:03 -07001065 validate_discovery_requests(self, http, "zoo", "v123", V1_DISCOVERY_URI)
1066
Joe Gregorioa98733f2011-09-16 10:12:28 -04001067
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001068class DiscoveryRetryFromHttp(unittest.TestCase):
1069 def test_repeated_500_retries_and_fails(self):
1070 http = HttpMockSequence(
1071 [
1072 ({"status": "500"}, read_datafile("500.json", "rb")),
1073 ({"status": "503"}, read_datafile("503.json", "rb")),
1074 ]
1075 )
1076 with self.assertRaises(HttpError):
1077 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001078 build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001079
1080 mocked_sleep.assert_called_once()
1081 # We also want to verify that we stayed with v1 discovery
1082 validate_discovery_requests(self, http, "zoo", "v1", V1_DISCOVERY_URI)
1083
1084 def test_v2_repeated_500_retries_and_fails(self):
1085 http = HttpMockSequence(
1086 [
1087 ({"status": "404"}, "Not found"), # last v1 discovery call
1088 ({"status": "500"}, read_datafile("500.json", "rb")),
1089 ({"status": "503"}, read_datafile("503.json", "rb")),
1090 ]
1091 )
1092 with self.assertRaises(HttpError):
1093 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001094 build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001095
1096 mocked_sleep.assert_called_once()
1097 # We also want to verify that we switched to v2 discovery
1098 validate_discovery_requests(self, http, "zoo", "v1", V2_DISCOVERY_URI)
1099
1100 def test_single_500_retries_and_succeeds(self):
1101 http = HttpMockSequence(
1102 [
1103 ({"status": "500"}, read_datafile("500.json", "rb")),
1104 ({"status": "200"}, read_datafile("zoo.json", "rb")),
1105 ]
1106 )
1107 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001108 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001109
1110 self.assertTrue(hasattr(zoo, "animals"))
1111 mocked_sleep.assert_called_once()
1112 # We also want to verify that we stayed with v1 discovery
1113 validate_discovery_requests(self, http, "zoo", "v1", V1_DISCOVERY_URI)
1114
1115 def test_single_500_then_404_retries_and_succeeds(self):
1116 http = HttpMockSequence(
1117 [
1118 ({"status": "500"}, read_datafile("500.json", "rb")),
1119 ({"status": "404"}, "Not found"), # last v1 discovery call
1120 ({"status": "200"}, read_datafile("zoo.json", "rb")),
1121 ]
1122 )
1123 with mock.patch("time.sleep") as mocked_sleep:
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001124 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001125
1126 self.assertTrue(hasattr(zoo, "animals"))
1127 mocked_sleep.assert_called_once()
1128 # We also want to verify that we switched to v2 discovery
1129 validate_discovery_requests(self, http, "zoo", "v1", V2_DISCOVERY_URI)
1130
1131
Takashi Matsuo30125122015-08-19 11:42:32 -07001132class DiscoveryFromAppEngineCache(unittest.TestCase):
Zev Goldstein09e64472020-05-14 16:29:20 -04001133 def setUp(self):
1134 self.old_environ = os.environ.copy()
1135 os.environ["APPENGINE_RUNTIME"] = "python27"
1136
1137 def tearDown(self):
1138 os.environ = self.old_environ
1139
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001140 def test_appengine_memcache(self):
1141 # Hack module import
1142 self.orig_import = __import__
1143 self.mocked_api = mock.MagicMock()
Takashi Matsuo30125122015-08-19 11:42:32 -07001144
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001145 def import_mock(name, *args, **kwargs):
1146 if name == "google.appengine.api":
1147 return self.mocked_api
1148 return self.orig_import(name, *args, **kwargs)
Takashi Matsuo30125122015-08-19 11:42:32 -07001149
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001150 import_fullname = "__builtin__.__import__"
1151 if sys.version_info[0] >= 3:
1152 import_fullname = "builtins.__import__"
Takashi Matsuo30125122015-08-19 11:42:32 -07001153
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001154 with mock.patch(import_fullname, side_effect=import_mock):
1155 namespace = "google-api-client"
1156 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001157
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001158 self.mocked_api.memcache.get.return_value = None
Takashi Matsuo30125122015-08-19 11:42:32 -07001159
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001160 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001161
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001162 # memcache.get is called once
1163 url = "https://www.googleapis.com/discovery/v1/apis/plus/v1/rest"
1164 self.mocked_api.memcache.get.assert_called_once_with(
1165 url, namespace=namespace
1166 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001167
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001168 # memcache.set is called once
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001169 content = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001170 self.mocked_api.memcache.set.assert_called_once_with(
1171 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace
1172 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001173
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001174 # Returns the cached content this time.
1175 self.mocked_api.memcache.get.return_value = content
Takashi Matsuo30125122015-08-19 11:42:32 -07001176
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001177 # Make sure the contents are returned from the cache.
1178 # (Otherwise it should through an error)
1179 self.http = HttpMock(None, {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001180
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001181 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001182
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001183 # memcache.get is called twice
1184 self.mocked_api.memcache.get.assert_has_calls(
1185 [
1186 mock.call(url, namespace=namespace),
1187 mock.call(url, namespace=namespace),
1188 ]
1189 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001190
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001191 # memcahce.set is called just once
1192 self.mocked_api.memcache.set.assert_called_once_with(
1193 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace
1194 )
Takashi Matsuo30125122015-08-19 11:42:32 -07001195
1196
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001197class DiscoveryFromStaticDocument(unittest.TestCase):
1198 def test_retrieve_from_local_when_static_discovery_true(self):
1199 http = HttpMockSequence([({"status": "400"}, "")])
1200 drive = build("drive", "v3", http=http, cache_discovery=False,
1201 static_discovery=True)
1202 self.assertIsNotNone(drive)
1203 self.assertTrue(hasattr(drive, "files"))
1204
1205 def test_retrieve_from_internet_when_static_discovery_false(self):
1206 http = HttpMockSequence([({"status": "400"}, "")])
1207 with self.assertRaises(HttpError):
1208 build("drive", "v3", http=http, cache_discovery=False,
1209 static_discovery=False)
1210
1211 def test_unknown_api_when_static_discovery_true(self):
1212 with self.assertRaises(UnknownApiNameOrVersion):
1213 build("doesnotexist", "v3", cache_discovery=False,
1214 static_discovery=True)
1215
1216
Takashi Matsuo30125122015-08-19 11:42:32 -07001217class DictCache(Cache):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001218 def __init__(self):
1219 self.d = {}
1220
1221 def get(self, url):
1222 return self.d.get(url, None)
1223
1224 def set(self, url, content):
1225 self.d[url] = content
1226
1227 def contains(self, url):
1228 return url in self.d
Takashi Matsuo30125122015-08-19 11:42:32 -07001229
1230
1231class DiscoveryFromFileCache(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001232 def test_file_based_cache(self):
1233 cache = mock.Mock(wraps=DictCache())
1234 with mock.patch(
1235 "googleapiclient.discovery_cache.autodetect", return_value=cache
1236 ):
1237 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001238
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001239 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001240
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001241 # cache.get is called once
1242 url = "https://www.googleapis.com/discovery/v1/apis/plus/v1/rest"
1243 cache.get.assert_called_once_with(url)
Takashi Matsuo30125122015-08-19 11:42:32 -07001244
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001245 # cache.set is called once
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001246 content = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001247 cache.set.assert_called_once_with(url, content)
Takashi Matsuo30125122015-08-19 11:42:32 -07001248
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001249 # Make sure there is a cache entry for the plus v1 discovery doc.
1250 self.assertTrue(cache.contains(url))
Takashi Matsuo30125122015-08-19 11:42:32 -07001251
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001252 # Make sure the contents are returned from the cache.
1253 # (Otherwise it should through an error)
1254 self.http = HttpMock(None, {"status": "200"})
Takashi Matsuo30125122015-08-19 11:42:32 -07001255
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001256 plus = build("plus", "v1", http=self.http, static_discovery=False)
Takashi Matsuo30125122015-08-19 11:42:32 -07001257
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001258 # cache.get is called twice
1259 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
Takashi Matsuo30125122015-08-19 11:42:32 -07001260
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001261 # cahce.set is called just once
1262 cache.set.assert_called_once_with(url, content)
Takashi Matsuo30125122015-08-19 11:42:32 -07001263
1264
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001265class Discovery(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001266 def test_method_error_checking(self):
1267 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001268 plus = build("plus", "v1", http=self.http, static_discovery=False)
Joe Gregorio2379ecc2010-10-26 10:51:28 -04001269
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001270 # Missing required parameters
1271 try:
1272 plus.activities().list()
1273 self.fail()
1274 except TypeError as e:
1275 self.assertTrue("Missing" in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001276
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001277 # Missing required parameters even if supplied as None.
1278 try:
1279 plus.activities().list(collection=None, userId=None)
1280 self.fail()
1281 except TypeError as e:
1282 self.assertTrue("Missing" in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001283
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001284 # Parameter doesn't match regex
1285 try:
1286 plus.activities().list(collection="not_a_collection_name", userId="me")
1287 self.fail()
1288 except TypeError as e:
1289 self.assertTrue("not an allowed value" in str(e))
Joe Gregorio2467afa2012-06-20 12:21:25 -04001290
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001291 # Unexpected parameter
1292 try:
1293 plus.activities().list(flubber=12)
1294 self.fail()
1295 except TypeError as e:
1296 self.assertTrue("unexpected" in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001297
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001298 def _check_query_types(self, request):
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001299 parsed = urllib.parse.urlparse(request.uri)
1300 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001301 self.assertEqual(q["q"], ["foo"])
1302 self.assertEqual(q["i"], ["1"])
1303 self.assertEqual(q["n"], ["1.0"])
1304 self.assertEqual(q["b"], ["false"])
1305 self.assertEqual(q["a"], ["[1, 2, 3]"])
1306 self.assertEqual(q["o"], ["{'a': 1}"])
1307 self.assertEqual(q["e"], ["bar"])
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001308
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001309 def test_type_coercion(self):
1310 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001311 zoo = build("zoo", "v1", http=http, static_discovery=False)
Joe Gregoriobee86832011-02-22 10:00:19 -05001312
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001313 request = zoo.query(
1314 q="foo", i=1.0, n=1.0, b=0, a=[1, 2, 3], o={"a": 1}, e="bar"
1315 )
1316 self._check_query_types(request)
1317 request = zoo.query(
1318 q="foo", i=1, n=1, b=False, a=[1, 2, 3], o={"a": 1}, e="bar"
1319 )
1320 self._check_query_types(request)
Joe Gregoriobee86832011-02-22 10:00:19 -05001321
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001322 request = zoo.query(
1323 q="foo", i="1", n="1", b="", a=[1, 2, 3], o={"a": 1}, e="bar", er="two"
1324 )
Joe Gregoriof863f7a2011-02-24 03:24:44 -05001325
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001326 request = zoo.query(
1327 q="foo",
1328 i="1",
1329 n="1",
1330 b="",
1331 a=[1, 2, 3],
1332 o={"a": 1},
1333 e="bar",
1334 er=["one", "three"],
1335 rr=["foo", "bar"],
1336 )
1337 self._check_query_types(request)
Joe Gregorio6804c7a2011-11-18 14:30:32 -05001338
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001339 # Five is right out.
1340 self.assertRaises(TypeError, zoo.query, er=["one", "five"])
Joe Gregoriobee86832011-02-22 10:00:19 -05001341
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001342 def test_optional_stack_query_parameters(self):
1343 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001344 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001345 request = zoo.query(trace="html", fields="description")
Craig Citro1e742822012-03-01 12:59:22 -08001346
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001347 parsed = urllib.parse.urlparse(request.uri)
1348 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001349 self.assertEqual(q["trace"], ["html"])
1350 self.assertEqual(q["fields"], ["description"])
Joe Gregorio13217952011-02-22 15:37:38 -05001351
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001352 def test_string_params_value_of_none_get_dropped(self):
1353 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001354 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001355 request = zoo.query(trace=None, fields="description")
Joe Gregoriof4153422011-03-18 22:45:18 -04001356
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001357 parsed = urllib.parse.urlparse(request.uri)
1358 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001359 self.assertFalse("trace" in q)
Joe Gregorio2467afa2012-06-20 12:21:25 -04001360
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001361 def test_model_added_query_parameters(self):
1362 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001363 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001364 request = zoo.animals().get(name="Lion")
Joe Gregorio4b4002f2012-06-14 15:41:01 -04001365
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001366 parsed = urllib.parse.urlparse(request.uri)
1367 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001368 self.assertEqual(q["alt"], ["json"])
1369 self.assertEqual(request.headers["accept"], "application/json")
Joe Gregorioe08a1662011-12-07 09:48:22 -05001370
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001371 def test_fallback_to_raw_model(self):
1372 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001373 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001374 request = zoo.animals().getmedia(name="Lion")
Joe Gregorioe08a1662011-12-07 09:48:22 -05001375
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001376 parsed = urllib.parse.urlparse(request.uri)
1377 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001378 self.assertTrue("alt" not in q)
1379 self.assertEqual(request.headers["accept"], "*/*")
Joe Gregorioe08a1662011-12-07 09:48:22 -05001380
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001381 def test_patch(self):
1382 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001383 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001384 request = zoo.animals().patch(name="lion", body='{"description": "foo"}')
Joe Gregorioe08a1662011-12-07 09:48:22 -05001385
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001386 self.assertEqual(request.method, "PATCH")
Joe Gregoriof4153422011-03-18 22:45:18 -04001387
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001388 def test_batch_request_from_discovery(self):
1389 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
1390 # zoo defines a batchPath
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001391 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001392 batch_request = zoo.new_batch_http_request()
1393 self.assertEqual(
1394 batch_request._batch_uri, "https://www.googleapis.com/batchZoo"
1395 )
Joe Gregoriof4153422011-03-18 22:45:18 -04001396
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001397 def test_batch_request_from_default(self):
1398 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
1399 # plus does not define a batchPath
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001400 plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001401 batch_request = plus.new_batch_http_request()
1402 self.assertEqual(batch_request._batch_uri, "https://www.googleapis.com/batch")
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001403
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001404 def test_tunnel_patch(self):
1405 http = HttpMockSequence(
1406 [
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001407 ({"status": "200"}, read_datafile("zoo.json", "rb")),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001408 ({"status": "200"}, "echo_request_headers_as_json"),
1409 ]
1410 )
1411 http = tunnel_patch(http)
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001412 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001413 resp = zoo.animals().patch(name="lion", body='{"description": "foo"}').execute()
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001414
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001415 self.assertTrue("x-http-method-override" in resp)
Joe Gregoriof4153422011-03-18 22:45:18 -04001416
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001417 def test_plus_resources(self):
1418 self.http = HttpMock(datafile("plus.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001419 plus = build("plus", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001420 self.assertTrue(getattr(plus, "activities"))
1421 self.assertTrue(getattr(plus, "people"))
Joe Gregorioca876e42011-02-22 19:39:42 -05001422
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001423 def test_oauth2client_credentials(self):
1424 credentials = mock.Mock(spec=GoogleCredentials)
1425 credentials.create_scoped_required.return_value = False
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001426
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001427 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001428 service = build_from_document(discovery, credentials=credentials)
1429 self.assertEqual(service._http, credentials.authorize.return_value)
Orest Bolohane92c9002014-05-30 11:15:43 -07001430
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001431 def test_google_auth_credentials(self):
1432 credentials = mock.Mock(spec=google.auth.credentials.Credentials)
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001433 discovery = read_datafile("plus.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001434 service = build_from_document(discovery, credentials=credentials)
Orest Bolohane92c9002014-05-30 11:15:43 -07001435
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001436 self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp)
1437 self.assertEqual(service._http.credentials, credentials)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -08001438
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001439 def test_no_scopes_no_credentials(self):
1440 # Zoo doesn't have scopes
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001441 discovery = read_datafile("zoo.json")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001442 service = build_from_document(discovery)
1443 # Should be an ordinary httplib2.Http instance and not AuthorizedHttp.
1444 self.assertIsInstance(service._http, httplib2.Http)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -08001445
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001446 def test_full_featured(self):
1447 # Zoo should exercise all discovery facets
1448 # and should also have no future.json file.
1449 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001450 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001451 self.assertTrue(getattr(zoo, "animals"))
Orest Bolohane92c9002014-05-30 11:15:43 -07001452
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001453 request = zoo.animals().list(name="bat", projection="full")
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001454 parsed = urllib.parse.urlparse(request.uri)
1455 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001456 self.assertEqual(q["name"], ["bat"])
1457 self.assertEqual(q["projection"], ["full"])
Joe Gregoriof863f7a2011-02-24 03:24:44 -05001458
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001459 def test_nested_resources(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 self.assertTrue(getattr(zoo, "animals"))
1463 request = zoo.my().favorites().list(max_results="5")
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001464 parsed = urllib.parse.urlparse(request.uri)
1465 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001466 self.assertEqual(q["max-results"], ["5"])
Joe Gregorio2379ecc2010-10-26 10:51:28 -04001467
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001468 def test_top_level_functions(self):
1469 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001470 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001471 self.assertTrue(getattr(zoo, "query"))
1472 request = zoo.query(q="foo")
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001473 parsed = urllib.parse.urlparse(request.uri)
1474 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001475 self.assertEqual(q["q"], ["foo"])
Joe Gregoriod92897c2011-07-07 11:44:56 -04001476
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001477 def test_simple_media_uploads(self):
1478 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001479 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001480 doc = getattr(zoo.animals().insert, "__doc__")
1481 self.assertTrue("media_body" in doc)
Joe Gregorio2379ecc2010-10-26 10:51:28 -04001482
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001483 def test_simple_media_upload_no_max_size_provided(self):
1484 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001485 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001486 request = zoo.animals().crossbreed(media_body=datafile("small.png"))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001487 self.assertEqual("image/png", request.headers["content-type"])
1488 self.assertEqual(b"PNG", request.body[1:4])
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001489
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001490 def test_simple_media_raise_correct_exceptions(self):
1491 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001492 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -04001493
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001494 try:
1495 zoo.animals().insert(media_body=datafile("smiley.png"))
1496 self.fail("should throw exception if media is too large.")
1497 except MediaUploadSizeError:
1498 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001499
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001500 try:
1501 zoo.animals().insert(media_body=datafile("small.jpg"))
1502 self.fail("should throw exception if mimetype is unacceptable.")
1503 except UnacceptableMimeTypeError:
1504 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001505
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001506 def test_simple_media_good_upload(self):
1507 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001508 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001509
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001510 request = zoo.animals().insert(media_body=datafile("small.png"))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001511 self.assertEqual("image/png", request.headers["content-type"])
1512 self.assertEqual(b"PNG", request.body[1:4])
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001513 assertUrisEqual(
1514 self,
1515 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json",
1516 request.uri,
1517 )
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001518
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001519 def test_simple_media_unknown_mimetype(self):
1520 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001521 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001522
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001523 try:
1524 zoo.animals().insert(media_body=datafile("small-png"))
1525 self.fail("should throw exception if mimetype is unknown.")
1526 except UnknownFileType:
1527 pass
Brian J. Watson38051ac2016-10-25 07:53:08 -07001528
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001529 request = zoo.animals().insert(
1530 media_body=datafile("small-png"), media_mime_type="image/png"
1531 )
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001532 self.assertEqual("image/png", request.headers["content-type"])
1533 self.assertEqual(b"PNG", request.body[1:4])
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001534 assertUrisEqual(
1535 self,
1536 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json",
1537 request.uri,
1538 )
Brian J. Watson38051ac2016-10-25 07:53:08 -07001539
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001540 def test_multipart_media_raise_correct_exceptions(self):
1541 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001542 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Brian J. Watson38051ac2016-10-25 07:53:08 -07001543
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001544 try:
1545 zoo.animals().insert(media_body=datafile("smiley.png"), body={})
1546 self.fail("should throw exception if media is too large.")
1547 except MediaUploadSizeError:
1548 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001549
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001550 try:
1551 zoo.animals().insert(media_body=datafile("small.jpg"), body={})
1552 self.fail("should throw exception if mimetype is unacceptable.")
1553 except UnacceptableMimeTypeError:
1554 pass
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001555
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001556 def test_multipart_media_good_upload(self, static_discovery=False):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001557 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001558 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001559
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001560 request = zoo.animals().insert(media_body=datafile("small.png"), body={})
1561 self.assertTrue(request.headers["content-type"].startswith("multipart/related"))
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001562 contents = read_datafile("small.png", "rb")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001563 boundary = re.match(b"--=+([^=]+)", request.body).group(1)
1564 self.assertEqual(
1565 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
1566 b"--==============="
1567 + boundary
1568 + b"==\n"
1569 + b"Content-Type: application/json\n"
1570 + b"MIME-Version: 1.0\n\n"
1571 + b'{"data": {}}\n'
1572 + b"--==============="
1573 + boundary
1574 + b"==\n"
1575 + b"Content-Type: image/png\n"
1576 + b"MIME-Version: 1.0\n"
1577 + b"Content-Transfer-Encoding: binary\n\n"
1578 + contents
1579 + b"\n--==============="
1580 + boundary
1581 + b"==--",
1582 )
1583 assertUrisEqual(
1584 self,
1585 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json",
1586 request.uri,
1587 )
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001588
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001589 def test_media_capable_method_without_media(self):
1590 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001591 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001592
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001593 request = zoo.animals().insert(body={})
1594 self.assertTrue(request.headers["content-type"], "application/json")
Joe Gregoriofdf7c802011-06-30 12:33:38 -04001595
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001596 def test_resumable_multipart_media_good_upload(self):
1597 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001598 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001599
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001600 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1601 request = zoo.animals().insert(media_body=media_upload, body={})
1602 self.assertTrue(request.headers["content-type"].startswith("application/json"))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001603 self.assertEqual('{"data": {}}', request.body)
1604 self.assertEqual(media_upload, request.resumable)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001605
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001606 self.assertEqual("image/png", request.resumable.mimetype())
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001607
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001608 self.assertNotEqual(request.body, None)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001609 self.assertEqual(request.resumable_uri, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001610
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001611 http = HttpMockSequence(
1612 [
1613 ({"status": "200", "location": "http://upload.example.com"}, ""),
1614 ({"status": "308", "location": "http://upload.example.com/2"}, ""),
1615 (
1616 {
1617 "status": "308",
1618 "location": "http://upload.example.com/3",
1619 "range": "0-12",
1620 },
1621 "",
1622 ),
1623 (
1624 {
1625 "status": "308",
1626 "location": "http://upload.example.com/4",
1627 "range": "0-%d" % (media_upload.size() - 2),
1628 },
1629 "",
1630 ),
1631 ({"status": "200"}, '{"foo": "bar"}'),
1632 ]
1633 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001634
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001635 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001636 self.assertEqual(None, body)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001637 self.assertTrue(isinstance(status, MediaUploadProgress))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001638 self.assertEqual(0, status.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001639
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001640 # Two requests should have been made and the resumable_uri should have been
1641 # updated for each one.
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001642 self.assertEqual(request.resumable_uri, "http://upload.example.com/2")
1643 self.assertEqual(media_upload, request.resumable)
1644 self.assertEqual(0, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001645
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001646 # This next chuck call should upload the first chunk
1647 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001648 self.assertEqual(request.resumable_uri, "http://upload.example.com/3")
1649 self.assertEqual(media_upload, request.resumable)
1650 self.assertEqual(13, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001651
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001652 # This call will upload the next chunk
1653 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001654 self.assertEqual(request.resumable_uri, "http://upload.example.com/4")
1655 self.assertEqual(media_upload.size() - 1, request.resumable_progress)
1656 self.assertEqual('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001657
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001658 # Final call to next_chunk should complete the upload.
1659 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001660 self.assertEqual(body, {"foo": "bar"})
1661 self.assertEqual(status, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001662
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001663 def test_resumable_media_good_upload(self):
1664 """Not a multipart upload."""
1665 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001666 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001667
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001668 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1669 request = zoo.animals().insert(media_body=media_upload, body=None)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001670 self.assertEqual(media_upload, request.resumable)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001671
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001672 self.assertEqual("image/png", request.resumable.mimetype())
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001673
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001674 self.assertEqual(request.body, None)
1675 self.assertEqual(request.resumable_uri, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001676
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001677 http = HttpMockSequence(
1678 [
1679 ({"status": "200", "location": "http://upload.example.com"}, ""),
1680 (
1681 {
1682 "status": "308",
1683 "location": "http://upload.example.com/2",
1684 "range": "0-12",
1685 },
1686 "",
1687 ),
1688 (
1689 {
1690 "status": "308",
1691 "location": "http://upload.example.com/3",
1692 "range": "0-%d" % (media_upload.size() - 2),
1693 },
1694 "",
1695 ),
1696 ({"status": "200"}, '{"foo": "bar"}'),
1697 ]
1698 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001699
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001700 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001701 self.assertEqual(None, body)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001702 self.assertTrue(isinstance(status, MediaUploadProgress))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001703 self.assertEqual(13, status.resumable_progress)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001704
1705 # Two requests should have been made and the resumable_uri should have been
1706 # updated for each one.
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001707 self.assertEqual(request.resumable_uri, "http://upload.example.com/2")
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001708
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001709 self.assertEqual(media_upload, request.resumable)
1710 self.assertEqual(13, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001711
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001712 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001713 self.assertEqual(request.resumable_uri, "http://upload.example.com/3")
1714 self.assertEqual(media_upload.size() - 1, request.resumable_progress)
1715 self.assertEqual(request.body, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001716
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001717 # Final call to next_chunk should complete the upload.
1718 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001719 self.assertEqual(body, {"foo": "bar"})
1720 self.assertEqual(status, None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001721
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001722 def test_resumable_media_good_upload_from_execute(self):
1723 """Not a multipart upload."""
1724 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001725 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001726
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001727 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1728 request = zoo.animals().insert(media_body=media_upload, body=None)
1729 assertUrisEqual(
1730 self,
1731 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json",
1732 request.uri,
1733 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001734
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001735 http = HttpMockSequence(
1736 [
1737 ({"status": "200", "location": "http://upload.example.com"}, ""),
1738 (
1739 {
1740 "status": "308",
1741 "location": "http://upload.example.com/2",
1742 "range": "0-12",
1743 },
1744 "",
1745 ),
1746 (
1747 {
1748 "status": "308",
1749 "location": "http://upload.example.com/3",
1750 "range": "0-%d" % media_upload.size(),
1751 },
1752 "",
1753 ),
1754 ({"status": "200"}, '{"foo": "bar"}'),
1755 ]
1756 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001757
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001758 body = request.execute(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001759 self.assertEqual(body, {"foo": "bar"})
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_first_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 [({"status": "400", "location": "http://upload.example.com"}, "")]
1771 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001772
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001773 try:
1774 request.execute(http=http)
1775 self.fail("Should have raised ResumableUploadError.")
1776 except ResumableUploadError as e:
1777 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001778
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001779 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
1780 """Not a multipart upload."""
1781 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001782 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001783
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001784 media_upload = MediaFileUpload(datafile("small.png"), resumable=True)
1785 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001786
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001787 http = HttpMockSequence(
1788 [
1789 ({"status": "200", "location": "http://upload.example.com"}, ""),
1790 ({"status": "400"}, ""),
1791 ]
1792 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001793
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001794 self.assertRaises(HttpError, request.execute, http=http)
1795 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001796
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001797 http = HttpMockSequence(
1798 [
1799 ({"status": "308", "range": "0-5"}, ""),
1800 ({"status": "308", "range": "0-6"}, ""),
1801 ]
1802 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001803
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001804 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001805 self.assertEqual(
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001806 status.resumable_progress,
1807 7,
1808 "Should have first checked length and then tried to PUT more.",
1809 )
1810 self.assertFalse(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001811
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001812 # Put it back in an error state.
1813 http = HttpMockSequence([({"status": "400"}, "")])
1814 self.assertRaises(HttpError, request.execute, http=http)
1815 self.assertTrue(request._in_error_state)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001816
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001817 # Pretend the last request that 400'd actually succeeded.
1818 http = HttpMockSequence([({"status": "200"}, '{"foo": "bar"}')])
1819 status, body = request.next_chunk(http=http)
1820 self.assertEqual(body, {"foo": "bar"})
Joe Gregorio910b9b12012-06-12 09:36:30 -04001821
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001822 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1823 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001824 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001825
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001826 # Set up a seekable stream and try to upload in single chunk.
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001827 fd = io.BytesIO(b'01234"56789"')
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001828 media_upload = MediaIoBaseUpload(
1829 fd=fd, mimetype="text/plain", chunksize=-1, resumable=True
1830 )
Joe Gregorio910b9b12012-06-12 09:36:30 -04001831
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001832 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001833
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001834 # The single chunk fails, restart at the right point.
1835 http = HttpMockSequence(
1836 [
1837 ({"status": "200", "location": "http://upload.example.com"}, ""),
1838 (
1839 {
1840 "status": "308",
1841 "location": "http://upload.example.com/2",
1842 "range": "0-4",
1843 },
1844 "",
1845 ),
1846 ({"status": "200"}, "echo_request_body"),
1847 ]
1848 )
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001849
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001850 body = request.execute(http=http)
1851 self.assertEqual("56789", body)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001852
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001853 def test_media_io_base_stream_chunksize_resume(self):
1854 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001855 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001856
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001857 # Set up a seekable stream and try to upload in chunks.
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001858 fd = io.BytesIO(b"0123456789")
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001859 media_upload = MediaIoBaseUpload(
1860 fd=fd, mimetype="text/plain", chunksize=5, resumable=True
1861 )
Joe Gregorio5c120db2012-08-23 09:13:55 -04001862
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001863 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001864
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001865 # The single chunk fails, pull the content sent out of the exception.
1866 http = HttpMockSequence(
1867 [
1868 ({"status": "200", "location": "http://upload.example.com"}, ""),
1869 ({"status": "400"}, "echo_request_body"),
1870 ]
1871 )
Pat Ferateed9affd2015-03-03 16:03:15 -08001872
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001873 try:
1874 body = request.execute(http=http)
1875 except HttpError as e:
1876 self.assertEqual(b"01234", e.content)
Pat Ferateed9affd2015-03-03 16:03:15 -08001877
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001878 def test_resumable_media_handle_uploads_of_unknown_size(self):
1879 http = HttpMockSequence(
1880 [
1881 ({"status": "200", "location": "http://upload.example.com"}, ""),
1882 ({"status": "200"}, "echo_request_headers_as_json"),
1883 ]
1884 )
Pat Ferateed9affd2015-03-03 16:03:15 -08001885
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001886 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001887 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001888
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001889 # Create an upload that doesn't know the full size of the media.
1890 class IoBaseUnknownLength(MediaUpload):
1891 def chunksize(self):
1892 return 10
Joe Gregorio910b9b12012-06-12 09:36:30 -04001893
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001894 def mimetype(self):
1895 return "image/png"
Joe Gregorio910b9b12012-06-12 09:36:30 -04001896
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001897 def size(self):
1898 return None
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001899
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001900 def resumable(self):
1901 return True
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001902
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001903 def getbytes(self, begin, length):
1904 return "0123456789"
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001905
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001906 upload = IoBaseUnknownLength()
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001907
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001908 request = zoo.animals().insert(media_body=upload, body=None)
1909 status, body = request.next_chunk(http=http)
1910 self.assertEqual(body, {"Content-Range": "bytes 0-9/*", "Content-Length": "10"})
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001911
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001912 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1913 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001914 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001915
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001916 class IoBaseHasStream(MediaUpload):
1917 def chunksize(self):
1918 return 10
Joe Gregorio44454e42012-06-15 08:38:53 -04001919
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001920 def mimetype(self):
1921 return "image/png"
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001922
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001923 def size(self):
1924 return None
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001925
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001926 def resumable(self):
1927 return True
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001928
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001929 def getbytes(self, begin, length):
1930 return "0123456789"
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001931
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001932 def has_stream(self):
1933 return True
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001934
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001935 def stream(self):
1936 raise NotImplementedError()
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001937
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001938 upload = IoBaseHasStream()
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001939
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001940 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001941
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001942 sys.version_info = (2, 6, 5, "final", 0)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001943
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001944 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001945
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001946 # This should raise an exception because stream() will be called.
1947 http = HttpMockSequence(
1948 [
1949 ({"status": "200", "location": "http://upload.example.com"}, ""),
1950 ({"status": "200"}, "echo_request_headers_as_json"),
1951 ]
1952 )
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001953
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001954 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001955
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001956 sys.version_info = orig_version
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001957
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001958 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1959 http = HttpMockSequence(
1960 [
1961 ({"status": "200", "location": "http://upload.example.com"}, ""),
1962 ({"status": "200"}, "echo_request_headers_as_json"),
1963 ]
1964 )
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001965
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001966 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001967 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001968
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001969 fd = io.BytesIO(b"data goes here")
Joe Gregorio44454e42012-06-15 08:38:53 -04001970
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001971 # Create an upload that doesn't know the full size of the media.
1972 upload = MediaIoBaseUpload(
1973 fd=fd, mimetype="image/png", chunksize=15, resumable=True
1974 )
Joe Gregorio44454e42012-06-15 08:38:53 -04001975
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001976 request = zoo.animals().insert(media_body=upload, body=None)
1977 status, body = request.next_chunk(http=http)
1978 self.assertEqual(
1979 body, {"Content-Range": "bytes 0-13/14", "Content-Length": "14"}
1980 )
Joe Gregorio44454e42012-06-15 08:38:53 -04001981
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001982 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1983 http = HttpMockSequence(
1984 [
1985 ({"status": "200", "location": "http://upload.example.com"}, ""),
1986 ({"status": "400"}, ""),
1987 ]
1988 )
Joe Gregorio44454e42012-06-15 08:38:53 -04001989
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001990 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05001991 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001992
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001993 # Create an upload that doesn't know the full size of the media.
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04001994 fd = io.BytesIO(b"data goes here")
Joe Gregorio910b9b12012-06-12 09:36:30 -04001995
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001996 upload = MediaIoBaseUpload(
1997 fd=fd, mimetype="image/png", chunksize=500, resumable=True
1998 )
Joe Gregorio910b9b12012-06-12 09:36:30 -04001999
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002000 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio910b9b12012-06-12 09:36:30 -04002001
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002002 # Put it in an error state.
2003 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04002004
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002005 http = HttpMockSequence(
2006 [({"status": "400", "range": "0-5"}, "echo_request_headers_as_json")]
2007 )
2008 try:
2009 # Should resume the upload by first querying the status of the upload.
2010 request.next_chunk(http=http)
2011 except HttpError as e:
2012 expected = {"Content-Range": "bytes */14", "content-length": "0"}
2013 self.assertEqual(
2014 expected,
2015 json.loads(e.content.decode("utf-8")),
2016 "Should send an empty body when requesting the current upload status.",
2017 )
Joe Gregorio910b9b12012-06-12 09:36:30 -04002018
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002019 def test_pickle(self):
2020 sorted_resource_keys = [
2021 "_baseUrl",
2022 "_developerKey",
2023 "_dynamic_attrs",
2024 "_http",
2025 "_model",
2026 "_requestBuilder",
2027 "_resourceDesc",
2028 "_rootDesc",
2029 "_schema",
2030 "animals",
2031 "global_",
2032 "load",
2033 "loadNoTemplate",
2034 "my",
2035 "new_batch_http_request",
2036 "query",
2037 "scopedAnimals",
2038 ]
Joe Gregorio910b9b12012-06-12 09:36:30 -04002039
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002040 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002041 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002042 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05002043
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002044 pickled_zoo = pickle.dumps(zoo)
2045 new_zoo = pickle.loads(pickled_zoo)
2046 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
2047 self.assertTrue(hasattr(new_zoo, "animals"))
2048 self.assertTrue(callable(new_zoo.animals))
2049 self.assertTrue(hasattr(new_zoo, "global_"))
2050 self.assertTrue(callable(new_zoo.global_))
2051 self.assertTrue(hasattr(new_zoo, "load"))
2052 self.assertTrue(callable(new_zoo.load))
2053 self.assertTrue(hasattr(new_zoo, "loadNoTemplate"))
2054 self.assertTrue(callable(new_zoo.loadNoTemplate))
2055 self.assertTrue(hasattr(new_zoo, "my"))
2056 self.assertTrue(callable(new_zoo.my))
2057 self.assertTrue(hasattr(new_zoo, "query"))
2058 self.assertTrue(callable(new_zoo.query))
2059 self.assertTrue(hasattr(new_zoo, "scopedAnimals"))
2060 self.assertTrue(callable(new_zoo.scopedAnimals))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002061
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002062 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
2063 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
2064 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
2065 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
2066 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
2067 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
2068 # _http, _model and _schema won't be equal since we will get new
2069 # instances upon un-pickling
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002070
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002071 def _dummy_zoo_request(self):
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07002072 zoo_contents = read_datafile("zoo.json")
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002073
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002074 zoo_uri = uritemplate.expand(DISCOVERY_URI, {"api": "zoo", "apiVersion": "v1"})
2075 if "REMOTE_ADDR" in os.environ:
2076 zoo_uri = util._add_query_parameter(
2077 zoo_uri, "userIp", os.environ["REMOTE_ADDR"]
2078 )
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002079
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002080 http = build_http()
2081 original_request = http.request
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002082
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002083 def wrapped_request(uri, method="GET", *args, **kwargs):
2084 if uri == zoo_uri:
2085 return httplib2.Response({"status": "200"}), zoo_contents
2086 return original_request(uri, method=method, *args, **kwargs)
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002087
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002088 http.request = wrapped_request
2089 return http
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002090
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002091 def _dummy_token(self):
2092 access_token = "foo"
2093 client_id = "some_client_id"
2094 client_secret = "cOuDdkfjxxnv+"
2095 refresh_token = "1/0/a.df219fjls0"
2096 token_expiry = datetime.datetime.utcnow()
2097 user_agent = "refresh_checker/1.0"
2098 return OAuth2Credentials(
2099 access_token,
2100 client_id,
2101 client_secret,
2102 refresh_token,
2103 token_expiry,
2104 GOOGLE_TOKEN_URI,
2105 user_agent,
2106 )
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002107
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002108 def test_pickle_with_credentials(self):
2109 credentials = self._dummy_token()
2110 http = self._dummy_zoo_request()
2111 http = credentials.authorize(http)
2112 self.assertTrue(hasattr(http.request, "credentials"))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002113
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002114 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002115 pickled_zoo = pickle.dumps(zoo)
2116 new_zoo = pickle.loads(pickled_zoo)
2117 self.assertEqual(sorted(zoo.__dict__.keys()), sorted(new_zoo.__dict__.keys()))
2118 new_http = new_zoo._http
2119 self.assertFalse(hasattr(new_http.request, "credentials"))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05002120
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002121 def test_resumable_media_upload_no_content(self):
2122 self.http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002123 zoo = build("zoo", "v1", http=self.http, static_discovery=False)
andrewnestera4a44cf2017-03-31 16:09:31 +03002124
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002125 media_upload = MediaFileUpload(datafile("empty"), resumable=True)
2126 request = zoo.animals().insert(media_body=media_upload, body=None)
andrewnestera4a44cf2017-03-31 16:09:31 +03002127
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07002128 self.assertEqual(media_upload, request.resumable)
2129 self.assertEqual(request.body, None)
2130 self.assertEqual(request.resumable_uri, None)
andrewnestera4a44cf2017-03-31 16:09:31 +03002131
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002132 http = HttpMockSequence(
2133 [
2134 ({"status": "200", "location": "http://upload.example.com"}, ""),
2135 (
2136 {
2137 "status": "308",
2138 "location": "http://upload.example.com/2",
2139 "range": "0-0",
2140 },
2141 "",
2142 ),
2143 ]
2144 )
andrewnestera4a44cf2017-03-31 16:09:31 +03002145
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002146 status, body = request.next_chunk(http=http)
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07002147 self.assertEqual(None, body)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002148 self.assertTrue(isinstance(status, MediaUploadProgress))
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07002149 self.assertEqual(0, status.progress())
andrewnestera4a44cf2017-03-31 16:09:31 +03002150
Joe Gregorio708388c2012-06-15 13:43:04 -04002151
Joe Gregorioc5c5a372010-09-22 11:42:32 -04002152class Next(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002153 def test_next_successful_none_on_no_next_page_token(self):
2154 self.http = HttpMock(datafile("tasks.json"), {"status": "200"})
2155 tasks = build("tasks", "v1", http=self.http)
2156 request = tasks.tasklists().list()
2157 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
Joe Gregorio00cf1d92010-09-27 09:22:03 -04002158
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002159 def test_next_successful_none_on_empty_page_token(self):
2160 self.http = HttpMock(datafile("tasks.json"), {"status": "200"})
2161 tasks = build("tasks", "v1", http=self.http)
2162 request = tasks.tasklists().list()
2163 next_request = tasks.tasklists().list_next(request, {"nextPageToken": ""})
2164 self.assertEqual(None, next_request)
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(self):
2167 self.http = HttpMock(datafile("tasks.json"), {"status": "200"})
2168 tasks = build("tasks", "v1", http=self.http)
2169 request = tasks.tasklists().list()
2170 next_request = tasks.tasklists().list_next(request, {"nextPageToken": "123abc"})
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04002171 parsed = urllib.parse.urlparse(next_request.uri)
2172 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002173 self.assertEqual(q["pageToken"][0], "123abc")
Son Dinh2a9a2132015-07-23 16:30:56 +00002174
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002175 def test_next_successful_with_next_page_token_alternate_name(self):
2176 self.http = HttpMock(datafile("bigquery.json"), {"status": "200"})
2177 bigquery = build("bigquery", "v2", http=self.http)
2178 request = bigquery.tabledata().list(datasetId="", projectId="", tableId="")
2179 next_request = bigquery.tabledata().list_next(request, {"pageToken": "123abc"})
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04002180 parsed = urllib.parse.urlparse(next_request.uri)
2181 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002182 self.assertEqual(q["pageToken"][0], "123abc")
Joe Gregorio3c676f92011-07-25 10:38:14 -04002183
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002184 def test_next_successful_with_next_page_token_in_body(self):
2185 self.http = HttpMock(datafile("logging.json"), {"status": "200"})
2186 logging = build("logging", "v2", http=self.http)
2187 request = logging.entries().list(body={})
2188 next_request = logging.entries().list_next(request, {"nextPageToken": "123abc"})
2189 body = JsonModel().deserialize(next_request.body)
2190 self.assertEqual(body["pageToken"], "123abc")
David Schweikert8019f2f2021-06-08 16:54:44 +02002191 # The body is changed, make sure that body_length is changed too (see
2192 # github #1403)
2193 self.assertEqual(next_request.body_size, len(next_request.body))
Thomas Coffee20af04d2017-02-10 15:24:44 -08002194
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002195 def test_next_with_method_with_no_properties(self):
2196 self.http = HttpMock(datafile("latitude.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002197 service = build("latitude", "v1", http=self.http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002198 service.currentLocation().get()
Thomas Coffee20af04d2017-02-10 15:24:44 -08002199
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002200 def test_next_nonexistent_with_no_next_page_token(self):
2201 self.http = HttpMock(datafile("drive.json"), {"status": "200"})
2202 drive = build("drive", "v3", http=self.http)
2203 drive.changes().watch(body={})
2204 self.assertFalse(callable(getattr(drive.changes(), "watch_next", None)))
Thomas Coffee20af04d2017-02-10 15:24:44 -08002205
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002206 def test_next_successful_with_next_page_token_required(self):
2207 self.http = HttpMock(datafile("drive.json"), {"status": "200"})
2208 drive = build("drive", "v3", http=self.http)
2209 request = drive.changes().list(pageToken="startPageToken")
2210 next_request = drive.changes().list_next(request, {"nextPageToken": "123abc"})
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04002211 parsed = urllib.parse.urlparse(next_request.uri)
2212 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002213 self.assertEqual(q["pageToken"][0], "123abc")
Joe Gregorio00cf1d92010-09-27 09:22:03 -04002214
Joe Gregorioa98733f2011-09-16 10:12:28 -04002215
Joe Gregorio708388c2012-06-15 13:43:04 -04002216class MediaGet(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002217 def test_get_media(self):
2218 http = HttpMock(datafile("zoo.json"), {"status": "200"})
Anthonios Partheniou32d1c592021-01-14 18:48:59 -05002219 zoo = build("zoo", "v1", http=http, static_discovery=False)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002220 request = zoo.animals().get_media(name="Lion")
Joe Gregorio708388c2012-06-15 13:43:04 -04002221
Anthonios Partheniou9f7b4102021-07-23 12:18:25 -04002222 parsed = urllib.parse.urlparse(request.uri)
2223 q = urllib.parse.parse_qs(parsed.query)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002224 self.assertEqual(q["alt"], ["media"])
2225 self.assertEqual(request.headers["accept"], "*/*")
Joe Gregorio708388c2012-06-15 13:43:04 -04002226
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002227 http = HttpMockSequence([({"status": "200"}, "standing in for media")])
2228 response = request.execute(http=http)
2229 self.assertEqual(b"standing in for media", response)
Joe Gregorio708388c2012-06-15 13:43:04 -04002230
2231
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07002232if __name__ == "__main__":
2233 unittest.main()