1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# 4# Copyright 2014 Google Inc. All Rights Reserved. 5# 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 18 19"""Discovery document tests 20 21Unit tests for objects created from discovery documents. 22""" 23from __future__ import absolute_import 24 25__author__ = "[email protected] (Joe Gregorio)" 26 27from collections import defaultdict 28import copy 29import datetime 30import httplib2 31import io 32import itertools 33import json 34import os 35import pickle 36import re 37import sys 38import unittest 39import urllib 40 41from parameterized import parameterized 42import mock 43 44import google.auth.credentials 45from google.auth.transport import mtls 46from google.auth.exceptions import MutualTLSChannelError 47import google_auth_httplib2 48import google.api_core.exceptions 49 50from googleapiclient.discovery import _fix_up_media_upload 51from googleapiclient.discovery import _fix_up_method_description 52from googleapiclient.discovery import _fix_up_parameters 53from googleapiclient.discovery import _urljoin 54from 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 59from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE 60from googleapiclient.discovery import ResourceMethodParameters 61from googleapiclient.discovery import STACK_QUERY_PARAMETERS 62from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE 63from googleapiclient.discovery import V1_DISCOVERY_URI 64from googleapiclient.discovery import V2_DISCOVERY_URI 65from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE 66from googleapiclient.discovery_cache.base import Cache 67from googleapiclient.errors import HttpError 68from googleapiclient.errors import InvalidJsonError 69from googleapiclient.errors import MediaUploadSizeError 70from googleapiclient.errors import ResumableUploadError 71from googleapiclient.errors import UnacceptableMimeTypeError 72from googleapiclient.errors import UnknownApiNameOrVersion 73from googleapiclient.errors import UnknownFileType 74from googleapiclient.http import build_http 75from googleapiclient.http import BatchHttpRequest 76from 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 83from googleapiclient.model import JsonModel 84from googleapiclient.schema import Schemas 85from oauth2client import GOOGLE_TOKEN_URI 86from oauth2client.client import OAuth2Credentials, GoogleCredentials 87 88 89from googleapiclient import _helpers as util 90 91import uritemplate 92 93 94DATA_DIR = os.path.join(os.path.dirname(__file__), "data") 95 96 97def assertUrisEqual(testcase, expected, actual): 98 """Test that URIs are the same, up to reordering of query parameters.""" 99 expected = urllib.parse.urlparse(expected) 100 actual = urllib.parse.urlparse(actual) 101 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) 106 expected_query = urllib.parse.parse_qs(expected.query) 107 actual_query = urllib.parse.parse_qs(actual.query) 108 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]) 112 113 114def 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 122def validate_discovery_requests(testcase, http_mock, service_name, version, discovery): 123 """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] 129 assert_discovery_uri(testcase, actual_uri, service_name, version, discovery) 130 131 132def datafile(filename): 133 return os.path.join(DATA_DIR, filename) 134 135 136def read_datafile(filename, mode="r"): 137 with open(datafile(filename), mode=mode) as f: 138 return f.read() 139 140 141class SetupHttplib2(unittest.TestCase): 142 def test_retries(self): 143 # Merely loading googleapiclient.discovery should set the RETRIES to 1. 144 self.assertEqual(1, httplib2.RETRIES) 145 146 147class Utilities(unittest.TestCase): 148 def setUp(self): 149 self.zoo_root_desc = json.loads(read_datafile("zoo.json", "r")) 150 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) 154 155 def test_key2param(self): 156 self.assertEqual("max_results", key2param("max-results")) 157 self.assertEqual("x007_bond", key2param("007-bond")) 158 159 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc, schema): 160 self.assertEqual(method_desc["httpMethod"], http_method) 161 162 method_desc_copy = copy.deepcopy(method_desc) 163 self.assertEqual(method_desc, method_desc_copy) 164 165 parameters = _fix_up_parameters( 166 method_desc_copy, root_desc, http_method, schema 167 ) 168 169 self.assertNotEqual(method_desc, method_desc_copy) 170 171 for param_name in STACK_QUERY_PARAMETERS: 172 self.assertEqual( 173 STACK_QUERY_PARAMETER_DEFAULT_VALUE, parameters[param_name] 174 ) 175 176 for param_name, value in root_desc.get("parameters", {}).items(): 177 self.assertEqual(value, parameters[param_name]) 178 179 return parameters 180 181 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) 187 188 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) 194 195 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 } 204 } 205 no_payload_http_method = "DELETE" 206 with_payload_http_method = "PUT" 207 208 invalid_method_desc = {"response": "Who cares"} 209 valid_method_desc = { 210 "request": {"key1": "value1", "key2": "value2", "$ref": "Request"} 211 } 212 213 parameters = _fix_up_parameters( 214 invalid_method_desc, dummy_root_desc, no_payload_http_method, dummy_schema 215 ) 216 self.assertFalse("body" in parameters) 217 218 parameters = _fix_up_parameters( 219 valid_method_desc, dummy_root_desc, no_payload_http_method, dummy_schema 220 ) 221 self.assertFalse("body" in parameters) 222 223 parameters = _fix_up_parameters( 224 invalid_method_desc, dummy_root_desc, with_payload_http_method, dummy_schema 225 ) 226 self.assertFalse("body" in parameters) 227 228 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) 239 240 def test_fix_up_parameters_optional_body(self): 241 # Request with no parameters 242 dummy_schema = {"Request": {"properties": {}}} 243 method_desc = {"request": {"$ref": "Request"}} 244 245 parameters = _fix_up_parameters(method_desc, {}, "POST", dummy_schema) 246 247 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 ): 256 fake_root_desc = { 257 "rootUrl": "http://root/", 258 "servicePath": "fake/", 259 "mtlsRootUrl": "http://root/", 260 } 261 fake_path_url = "fake-path/" 262 263 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) 270 271 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 ) 276 277 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 ) 291 292 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 ) 307 308 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 ) 314 315 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 ) 331 332 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 ) 349 350 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 ) 363 364 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 ) 377 378 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"] 382 383 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"] 388 389 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)) 394 395 def test_ResourceMethodParameters_zoo_get(self): 396 parameters = ResourceMethodParameters(self.zoo_get_method_desc) 397 398 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) 422 423 def test_ResourceMethodParameters_zoo_animals_patch(self): 424 method_desc = self.zoo_animals_resource["methods"]["patch"] 425 parameters = ResourceMethodParameters(method_desc) 426 427 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, {}) 437 438 439class 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 446class DiscoveryErrors(unittest.TestCase): 447 def test_tests_should_be_run_with_strict_positional_enforcement(self): 448 try: 449 plus = build("plus", "v1", None, static_discovery=False) 450 self.fail("should have raised a TypeError exception over missing http=.") 451 except TypeError: 452 pass 453 454 def test_failed_to_parse_discovery_json(self): 455 self.http = HttpMock(datafile("malformed.json"), {"status": "200"}) 456 try: 457 plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False) 458 self.fail("should have raised an exception over malformed JSON.") 459 except InvalidJsonError: 460 pass 461 462 def test_unknown_api_name_or_version(self): 463 http = HttpMockSequence( 464 [ 465 ({"status": "404"}, read_datafile("zoo.json", "rb")), 466 ({"status": "404"}, read_datafile("zoo.json", "rb")), 467 ] 468 ) 469 with self.assertRaises(UnknownApiNameOrVersion): 470 plus = build("plus", "v1", http=http, cache_discovery=False) 471 472 def test_credentials_and_http_mutually_exclusive(self): 473 http = HttpMock(datafile("plus.json"), {"status": "200"}) 474 with self.assertRaises(ValueError): 475 build("plus", "v1", http=http, credentials=mock.sentinel.credentials, static_discovery=False) 476 477 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 ), 487 static_discovery=False, 488 ) 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 ), 499 static_discovery=False, 500 ) 501 502 503class DiscoveryFromDocument(unittest.TestCase): 504 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials) 505 506 def test_can_build_from_local_document(self): 507 discovery = read_datafile("plus.json") 508 plus = build_from_document( 509 discovery, 510 base="https://www.googleapis.com/", 511 credentials=self.MOCK_CREDENTIALS, 512 ) 513 self.assertIsNotNone(plus) 514 self.assertTrue(hasattr(plus, "activities")) 515 516 def test_can_build_from_local_deserialized_document(self): 517 discovery = read_datafile("plus.json") 518 discovery = json.loads(discovery) 519 plus = build_from_document( 520 discovery, 521 base="https://www.googleapis.com/", 522 credentials=self.MOCK_CREDENTIALS, 523 ) 524 self.assertIsNotNone(plus) 525 self.assertTrue(hasattr(plus, "activities")) 526 527 def test_building_with_base_remembers_base(self): 528 discovery = read_datafile("plus.json") 529 530 base = "https://www.example.com/" 531 plus = build_from_document( 532 discovery, base=base, credentials=self.MOCK_CREDENTIALS 533 ) 534 self.assertEqual("https://www.googleapis.com/plus/v1/", plus._baseUrl) 535 536 def test_building_with_optional_http_with_authorization(self): 537 discovery = read_datafile("plus.json") 538 plus = build_from_document( 539 discovery, 540 base="https://www.googleapis.com/", 541 credentials=self.MOCK_CREDENTIALS, 542 ) 543 544 # 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) 549 550 def test_building_with_optional_http_with_no_authorization(self): 551 discovery = read_datafile("plus.json") 552 # Cleanup auth field, so we would use plain http client 553 discovery = json.loads(discovery) 554 discovery["auth"] = {} 555 discovery = json.dumps(discovery) 556 557 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) 564 565 def test_building_with_explicit_http(self): 566 http = HttpMock() 567 discovery = read_datafile("plus.json") 568 plus = build_from_document( 569 discovery, base="https://www.googleapis.com/", http=http 570 ) 571 self.assertEqual(plus._http, http) 572 573 def test_building_with_developer_key_skips_adc(self): 574 discovery = read_datafile("plus.json") 575 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) 582 583 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") 593 594 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): 607 plus = build_from_document( 608 discovery, 609 base="https://www.googleapis.com/", 610 credentials=self.MOCK_CREDENTIALS, 611 ) 612 plus.close() 613 plus._http.close.assert_called_once() 614 615 def test_api_endpoint_override_from_client_options(self): 616 discovery = read_datafile("plus.json") 617 api_endpoint = "https://foo.googleapis.com/" 618 options = google.api_core.client_options.ClientOptions( 619 api_endpoint=api_endpoint 620 ) 621 plus = build_from_document( 622 discovery, client_options=options, credentials=self.MOCK_CREDENTIALS 623 ) 624 625 self.assertEqual(plus._baseUrl, api_endpoint) 626 627 def test_api_endpoint_override_from_client_options_mapping_object(self): 628 629 discovery = read_datafile("plus.json") 630 api_endpoint = "https://foo.googleapis.com/" 631 mapping_object = defaultdict(str) 632 mapping_object["api_endpoint"] = api_endpoint 633 plus = build_from_document( 634 discovery, client_options=mapping_object, credentials=self.MOCK_CREDENTIALS 635 ) 636 637 self.assertEqual(plus._baseUrl, api_endpoint) 638 639 def test_api_endpoint_override_from_client_options_dict(self): 640 discovery = read_datafile("plus.json") 641 api_endpoint = "https://foo.googleapis.com/" 642 plus = build_from_document( 643 discovery, 644 client_options={"api_endpoint": api_endpoint}, 645 credentials=self.MOCK_CREDENTIALS, 646 ) 647 648 self.assertEqual(plus._baseUrl, api_endpoint) 649 650 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 688 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, 698 always_use_jwt_access=True, 699 ) 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, 712 ) 713 _create_self_signed_jwt.assert_not_called() 714 715 716REGULAR_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 726 def check_http_client_cert(self, resource, has_client_cert="false"): 727 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("")) 731 if has_client_cert == "true": 732 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 742 @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): 753 discovery = read_datafile("plus.json") 754 755 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") 765 766 @parameterized.expand( 767 [ 768 ("never", "true"), 769 ("auto", "true"), 770 ("always", "true"), 771 ("never", "false"), 772 ("auto", "false"), 773 ("always", "false"), 774 ] 775 ) 776 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 ): 804 discovery = read_datafile("plus.json") 805 806 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) 822 823 @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): 834 # Test endpoint is not switched if user provided api endpoint 835 discovery = read_datafile("plus.json") 836 837 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") 854 855 @parameterized.expand( 856 [ 857 ("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), 863 ] 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, 874 use_client_cert, 875 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 ) 883 discovery = read_datafile("plus.json") 884 885 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) 900 901 @parameterized.expand( 902 [ 903 ("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), 909 ] 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( 915 self, use_mtls_env, use_client_cert, base_url, has_default_client_cert_source 916 ): 917 has_default_client_cert_source.return_value = False 918 discovery = read_datafile("plus.json") 919 920 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) 935 936 937class DiscoveryFromHttp(unittest.TestCase): 938 def setUp(self): 939 self.old_environ = os.environ.copy() 940 941 def tearDown(self): 942 os.environ = self.old_environ 943 944 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( 950 [({"status": "400"}, read_datafile("zoo.json", "rb"))] 951 ) 952 zoo = build( 953 "zoo", 954 "v1", 955 http=http, 956 developerKey=None, 957 discoveryServiceUrl="http://example.com" 958 ) 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") 962 963 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( 968 [({"status": "400"}, read_datafile("zoo.json", "rb"))] 969 ) 970 zoo = build( 971 "zoo", 972 "v1", 973 http=http, 974 developerKey=None, 975 discoveryServiceUrl="http://example.com" 976 ) 977 self.fail("Should have raised an exception.") 978 except HttpError as e: 979 self.assertEqual(e.uri, "http://example.com") 980 981 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( 986 [({"status": "400"}, read_datafile("zoo.json", "rb"))] 987 ) 988 zoo = build( 989 "zoo", 990 "v1", 991 http=http, 992 developerKey="foo", 993 discoveryServiceUrl="http://example.com", 994 static_discovery=False, 995 ) 996 self.fail("Should have raised an exception.") 997 except HttpError as e: 998 self.assertEqual(e.uri, "http://example.com?key=foo") 999 1000 def test_discovery_loading_from_v2_discovery_uri(self): 1001 http = HttpMockSequence( 1002 [ 1003 ({"status": "404"}, "Not found"), 1004 ({"status": "200"}, read_datafile("zoo.json", "rb")), 1005 ] 1006 ) 1007 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) 1008 self.assertTrue(hasattr(zoo, "animals")) 1009 1010 def test_api_endpoint_override_from_client_options(self): 1011 http = HttpMockSequence( 1012 [ 1013 ({"status": "404"}, "Not found"), 1014 ({"status": "200"}, read_datafile("zoo.json", "rb")), 1015 ] 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( 1022 "zoo", "v1", http=http, cache_discovery=False, client_options=options, static_discovery=False 1023 ) 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"), 1030 ({"status": "200"}, read_datafile("zoo.json", "rb")), 1031 ] 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}, 1040 static_discovery=False, 1041 ) 1042 self.assertEqual(zoo._baseUrl, api_endpoint) 1043 1044 def test_discovery_with_empty_version_uses_v2(self): 1045 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),]) 1046 build("zoo", version=None, http=http, cache_discovery=False, static_discovery=False) 1047 validate_discovery_requests(self, http, "zoo", None, V2_DISCOVERY_URI) 1048 1049 def test_discovery_with_empty_version_preserves_custom_uri(self): 1050 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),]) 1051 custom_discovery_uri = "https://foo.bar/$discovery" 1052 build( 1053 "zoo", 1054 version=None, 1055 http=http, 1056 cache_discovery=False, 1057 discoveryServiceUrl=custom_discovery_uri, 1058 static_discovery=False, 1059 ) 1060 validate_discovery_requests(self, http, "zoo", None, custom_discovery_uri) 1061 1062 def test_discovery_with_valid_version_uses_v1(self): 1063 http = HttpMockSequence([({"status": "200"}, read_datafile("zoo.json", "rb")),]) 1064 build("zoo", version="v123", http=http, cache_discovery=False, static_discovery=False) 1065 validate_discovery_requests(self, http, "zoo", "v123", V1_DISCOVERY_URI) 1066 1067 1068class 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: 1078 build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) 1079 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: 1094 build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) 1095 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: 1108 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) 1109 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: 1124 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) 1125 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 1132class DiscoveryFromAppEngineCache(unittest.TestCase): 1133 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 1140 def test_appengine_memcache(self): 1141 # Hack module import 1142 self.orig_import = __import__ 1143 self.mocked_api = mock.MagicMock() 1144 1145 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) 1149 1150 import_fullname = "__builtin__.__import__" 1151 if sys.version_info[0] >= 3: 1152 import_fullname = "builtins.__import__" 1153 1154 with mock.patch(import_fullname, side_effect=import_mock): 1155 namespace = "google-api-client" 1156 self.http = HttpMock(datafile("plus.json"), {"status": "200"}) 1157 1158 self.mocked_api.memcache.get.return_value = None 1159 1160 plus = build("plus", "v1", http=self.http, static_discovery=False) 1161 1162 # 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 ) 1167 1168 # memcache.set is called once 1169 content = read_datafile("plus.json") 1170 self.mocked_api.memcache.set.assert_called_once_with( 1171 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace 1172 ) 1173 1174 # Returns the cached content this time. 1175 self.mocked_api.memcache.get.return_value = content 1176 1177 # Make sure the contents are returned from the cache. 1178 # (Otherwise it should through an error) 1179 self.http = HttpMock(None, {"status": "200"}) 1180 1181 plus = build("plus", "v1", http=self.http, static_discovery=False) 1182 1183 # 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 ) 1190 1191 # 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 ) 1195 1196 1197class 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 1217class DictCache(Cache): 1218 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 1229 1230 1231class DiscoveryFromFileCache(unittest.TestCase): 1232 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"}) 1238 1239 plus = build("plus", "v1", http=self.http, static_discovery=False) 1240 1241 # 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) 1244 1245 # cache.set is called once 1246 content = read_datafile("plus.json") 1247 cache.set.assert_called_once_with(url, content) 1248 1249 # Make sure there is a cache entry for the plus v1 discovery doc. 1250 self.assertTrue(cache.contains(url)) 1251 1252 # Make sure the contents are returned from the cache. 1253 # (Otherwise it should through an error) 1254 self.http = HttpMock(None, {"status": "200"}) 1255 1256 plus = build("plus", "v1", http=self.http, static_discovery=False) 1257 1258 # cache.get is called twice 1259 cache.get.assert_has_calls([mock.call(url), mock.call(url)]) 1260 1261 # cahce.set is called just once 1262 cache.set.assert_called_once_with(url, content) 1263 1264 1265class Discovery(unittest.TestCase): 1266 def test_method_error_checking(self): 1267 self.http = HttpMock(datafile("plus.json"), {"status": "200"}) 1268 plus = build("plus", "v1", http=self.http, static_discovery=False) 1269 1270 # Missing required parameters 1271 try: 1272 plus.activities().list() 1273 self.fail() 1274 except TypeError as e: 1275 self.assertTrue("Missing" in str(e)) 1276 1277 # 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)) 1283 1284 # 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)) 1290 1291 # 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)) 1297 1298 def _check_query_types(self, request): 1299 parsed = urllib.parse.urlparse(request.uri) 1300 q = urllib.parse.parse_qs(parsed.query) 1301 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"]) 1308 1309 def test_type_coercion(self): 1310 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1311 zoo = build("zoo", "v1", http=http, static_discovery=False) 1312 1313 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) 1321 1322 request = zoo.query( 1323 q="foo", i="1", n="1", b="", a=[1, 2, 3], o={"a": 1}, e="bar", er="two" 1324 ) 1325 1326 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) 1338 1339 # Five is right out. 1340 self.assertRaises(TypeError, zoo.query, er=["one", "five"]) 1341 1342 def test_optional_stack_query_parameters(self): 1343 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1344 zoo = build("zoo", "v1", http=http, static_discovery=False) 1345 request = zoo.query(trace="html", fields="description") 1346 1347 parsed = urllib.parse.urlparse(request.uri) 1348 q = urllib.parse.parse_qs(parsed.query) 1349 self.assertEqual(q["trace"], ["html"]) 1350 self.assertEqual(q["fields"], ["description"]) 1351 1352 def test_string_params_value_of_none_get_dropped(self): 1353 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1354 zoo = build("zoo", "v1", http=http, static_discovery=False) 1355 request = zoo.query(trace=None, fields="description") 1356 1357 parsed = urllib.parse.urlparse(request.uri) 1358 q = urllib.parse.parse_qs(parsed.query) 1359 self.assertFalse("trace" in q) 1360 1361 def test_model_added_query_parameters(self): 1362 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1363 zoo = build("zoo", "v1", http=http, static_discovery=False) 1364 request = zoo.animals().get(name="Lion") 1365 1366 parsed = urllib.parse.urlparse(request.uri) 1367 q = urllib.parse.parse_qs(parsed.query) 1368 self.assertEqual(q["alt"], ["json"]) 1369 self.assertEqual(request.headers["accept"], "application/json") 1370 1371 def test_fallback_to_raw_model(self): 1372 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1373 zoo = build("zoo", "v1", http=http, static_discovery=False) 1374 request = zoo.animals().getmedia(name="Lion") 1375 1376 parsed = urllib.parse.urlparse(request.uri) 1377 q = urllib.parse.parse_qs(parsed.query) 1378 self.assertTrue("alt" not in q) 1379 self.assertEqual(request.headers["accept"], "*/*") 1380 1381 def test_patch(self): 1382 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1383 zoo = build("zoo", "v1", http=http, static_discovery=False) 1384 request = zoo.animals().patch(name="lion", body='{"description": "foo"}') 1385 1386 self.assertEqual(request.method, "PATCH") 1387 1388 def test_batch_request_from_discovery(self): 1389 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1390 # zoo defines a batchPath 1391 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1392 batch_request = zoo.new_batch_http_request() 1393 self.assertEqual( 1394 batch_request._batch_uri, "https://www.googleapis.com/batchZoo" 1395 ) 1396 1397 def test_batch_request_from_default(self): 1398 self.http = HttpMock(datafile("plus.json"), {"status": "200"}) 1399 # plus does not define a batchPath 1400 plus = build("plus", "v1", http=self.http, cache_discovery=False, static_discovery=False) 1401 batch_request = plus.new_batch_http_request() 1402 self.assertEqual(batch_request._batch_uri, "https://www.googleapis.com/batch") 1403 1404 def test_tunnel_patch(self): 1405 http = HttpMockSequence( 1406 [ 1407 ({"status": "200"}, read_datafile("zoo.json", "rb")), 1408 ({"status": "200"}, "echo_request_headers_as_json"), 1409 ] 1410 ) 1411 http = tunnel_patch(http) 1412 zoo = build("zoo", "v1", http=http, cache_discovery=False, static_discovery=False) 1413 resp = zoo.animals().patch(name="lion", body='{"description": "foo"}').execute() 1414 1415 self.assertTrue("x-http-method-override" in resp) 1416 1417 def test_plus_resources(self): 1418 self.http = HttpMock(datafile("plus.json"), {"status": "200"}) 1419 plus = build("plus", "v1", http=self.http, static_discovery=False) 1420 self.assertTrue(getattr(plus, "activities")) 1421 self.assertTrue(getattr(plus, "people")) 1422 1423 def test_oauth2client_credentials(self): 1424 credentials = mock.Mock(spec=GoogleCredentials) 1425 credentials.create_scoped_required.return_value = False 1426 1427 discovery = read_datafile("plus.json") 1428 service = build_from_document(discovery, credentials=credentials) 1429 self.assertEqual(service._http, credentials.authorize.return_value) 1430 1431 def test_google_auth_credentials(self): 1432 credentials = mock.Mock(spec=google.auth.credentials.Credentials) 1433 discovery = read_datafile("plus.json") 1434 service = build_from_document(discovery, credentials=credentials) 1435 1436 self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp) 1437 self.assertEqual(service._http.credentials, credentials) 1438 1439 def test_no_scopes_no_credentials(self): 1440 # Zoo doesn't have scopes 1441 discovery = read_datafile("zoo.json") 1442 service = build_from_document(discovery) 1443 # Should be an ordinary httplib2.Http instance and not AuthorizedHttp. 1444 self.assertIsInstance(service._http, httplib2.Http) 1445 1446 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"}) 1450 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1451 self.assertTrue(getattr(zoo, "animals")) 1452 1453 request = zoo.animals().list(name="bat", projection="full") 1454 parsed = urllib.parse.urlparse(request.uri) 1455 q = urllib.parse.parse_qs(parsed.query) 1456 self.assertEqual(q["name"], ["bat"]) 1457 self.assertEqual(q["projection"], ["full"]) 1458 1459 def test_nested_resources(self): 1460 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1461 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1462 self.assertTrue(getattr(zoo, "animals")) 1463 request = zoo.my().favorites().list(max_results="5") 1464 parsed = urllib.parse.urlparse(request.uri) 1465 q = urllib.parse.parse_qs(parsed.query) 1466 self.assertEqual(q["max-results"], ["5"]) 1467 1468 def test_top_level_functions(self): 1469 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1470 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1471 self.assertTrue(getattr(zoo, "query")) 1472 request = zoo.query(q="foo") 1473 parsed = urllib.parse.urlparse(request.uri) 1474 q = urllib.parse.parse_qs(parsed.query) 1475 self.assertEqual(q["q"], ["foo"]) 1476 1477 def test_simple_media_uploads(self): 1478 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1479 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1480 doc = getattr(zoo.animals().insert, "__doc__") 1481 self.assertTrue("media_body" in doc) 1482 1483 def test_simple_media_upload_no_max_size_provided(self): 1484 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1485 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1486 request = zoo.animals().crossbreed(media_body=datafile("small.png")) 1487 self.assertEqual("image/png", request.headers["content-type"]) 1488 self.assertEqual(b"PNG", request.body[1:4]) 1489 1490 def test_simple_media_raise_correct_exceptions(self): 1491 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1492 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1493 1494 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 1499 1500 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 1505 1506 def test_simple_media_good_upload(self): 1507 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1508 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1509 1510 request = zoo.animals().insert(media_body=datafile("small.png")) 1511 self.assertEqual("image/png", request.headers["content-type"]) 1512 self.assertEqual(b"PNG", request.body[1:4]) 1513 assertUrisEqual( 1514 self, 1515 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json", 1516 request.uri, 1517 ) 1518 1519 def test_simple_media_unknown_mimetype(self): 1520 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1521 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1522 1523 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 1528 1529 request = zoo.animals().insert( 1530 media_body=datafile("small-png"), media_mime_type="image/png" 1531 ) 1532 self.assertEqual("image/png", request.headers["content-type"]) 1533 self.assertEqual(b"PNG", request.body[1:4]) 1534 assertUrisEqual( 1535 self, 1536 "https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json", 1537 request.uri, 1538 ) 1539 1540 def test_multipart_media_raise_correct_exceptions(self): 1541 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1542 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1543 1544 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 1549 1550 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 1555 1556 def test_multipart_media_good_upload(self, static_discovery=False): 1557 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1558 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1559 1560 request = zoo.animals().insert(media_body=datafile("small.png"), body={}) 1561 self.assertTrue(request.headers["content-type"].startswith("multipart/related")) 1562 contents = read_datafile("small.png", "rb") 1563 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 ) 1588 1589 def test_media_capable_method_without_media(self): 1590 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1591 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1592 1593 request = zoo.animals().insert(body={}) 1594 self.assertTrue(request.headers["content-type"], "application/json") 1595 1596 def test_resumable_multipart_media_good_upload(self): 1597 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1598 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1599 1600 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")) 1603 self.assertEqual('{"data": {}}', request.body) 1604 self.assertEqual(media_upload, request.resumable) 1605 1606 self.assertEqual("image/png", request.resumable.mimetype()) 1607 1608 self.assertNotEqual(request.body, None) 1609 self.assertEqual(request.resumable_uri, None) 1610 1611 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 ) 1634 1635 status, body = request.next_chunk(http=http) 1636 self.assertEqual(None, body) 1637 self.assertTrue(isinstance(status, MediaUploadProgress)) 1638 self.assertEqual(0, status.resumable_progress) 1639 1640 # Two requests should have been made and the resumable_uri should have been 1641 # updated for each one. 1642 self.assertEqual(request.resumable_uri, "http://upload.example.com/2") 1643 self.assertEqual(media_upload, request.resumable) 1644 self.assertEqual(0, request.resumable_progress) 1645 1646 # This next chuck call should upload the first chunk 1647 status, body = request.next_chunk(http=http) 1648 self.assertEqual(request.resumable_uri, "http://upload.example.com/3") 1649 self.assertEqual(media_upload, request.resumable) 1650 self.assertEqual(13, request.resumable_progress) 1651 1652 # This call will upload the next chunk 1653 status, body = request.next_chunk(http=http) 1654 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) 1657 1658 # Final call to next_chunk should complete the upload. 1659 status, body = request.next_chunk(http=http) 1660 self.assertEqual(body, {"foo": "bar"}) 1661 self.assertEqual(status, None) 1662 1663 def test_resumable_media_good_upload(self): 1664 """Not a multipart upload.""" 1665 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1666 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1667 1668 media_upload = MediaFileUpload(datafile("small.png"), resumable=True) 1669 request = zoo.animals().insert(media_body=media_upload, body=None) 1670 self.assertEqual(media_upload, request.resumable) 1671 1672 self.assertEqual("image/png", request.resumable.mimetype()) 1673 1674 self.assertEqual(request.body, None) 1675 self.assertEqual(request.resumable_uri, None) 1676 1677 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 ) 1699 1700 status, body = request.next_chunk(http=http) 1701 self.assertEqual(None, body) 1702 self.assertTrue(isinstance(status, MediaUploadProgress)) 1703 self.assertEqual(13, status.resumable_progress) 1704 1705 # Two requests should have been made and the resumable_uri should have been 1706 # updated for each one. 1707 self.assertEqual(request.resumable_uri, "http://upload.example.com/2") 1708 1709 self.assertEqual(media_upload, request.resumable) 1710 self.assertEqual(13, request.resumable_progress) 1711 1712 status, body = request.next_chunk(http=http) 1713 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) 1716 1717 # Final call to next_chunk should complete the upload. 1718 status, body = request.next_chunk(http=http) 1719 self.assertEqual(body, {"foo": "bar"}) 1720 self.assertEqual(status, None) 1721 1722 def test_resumable_media_good_upload_from_execute(self): 1723 """Not a multipart upload.""" 1724 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1725 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1726 1727 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 ) 1734 1735 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 ) 1757 1758 body = request.execute(http=http) 1759 self.assertEqual(body, {"foo": "bar"}) 1760 1761 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"}) 1764 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1765 1766 media_upload = MediaFileUpload(datafile("small.png"), resumable=True) 1767 request = zoo.animals().insert(media_body=media_upload, body=None) 1768 1769 http = HttpMockSequence( 1770 [({"status": "400", "location": "http://upload.example.com"}, "")] 1771 ) 1772 1773 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) 1778 1779 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"}) 1782 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1783 1784 media_upload = MediaFileUpload(datafile("small.png"), resumable=True) 1785 request = zoo.animals().insert(media_body=media_upload, body=None) 1786 1787 http = HttpMockSequence( 1788 [ 1789 ({"status": "200", "location": "http://upload.example.com"}, ""), 1790 ({"status": "400"}, ""), 1791 ] 1792 ) 1793 1794 self.assertRaises(HttpError, request.execute, http=http) 1795 self.assertTrue(request._in_error_state) 1796 1797 http = HttpMockSequence( 1798 [ 1799 ({"status": "308", "range": "0-5"}, ""), 1800 ({"status": "308", "range": "0-6"}, ""), 1801 ] 1802 ) 1803 1804 status, body = request.next_chunk(http=http) 1805 self.assertEqual( 1806 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) 1811 1812 # 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) 1816 1817 # 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"}) 1821 1822 def test_media_io_base_stream_unlimited_chunksize_resume(self): 1823 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1824 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1825 1826 # Set up a seekable stream and try to upload in single chunk. 1827 fd = io.BytesIO(b'01234"56789"') 1828 media_upload = MediaIoBaseUpload( 1829 fd=fd, mimetype="text/plain", chunksize=-1, resumable=True 1830 ) 1831 1832 request = zoo.animals().insert(media_body=media_upload, body=None) 1833 1834 # 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 ) 1849 1850 body = request.execute(http=http) 1851 self.assertEqual("56789", body) 1852 1853 def test_media_io_base_stream_chunksize_resume(self): 1854 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1855 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1856 1857 # Set up a seekable stream and try to upload in chunks. 1858 fd = io.BytesIO(b"0123456789") 1859 media_upload = MediaIoBaseUpload( 1860 fd=fd, mimetype="text/plain", chunksize=5, resumable=True 1861 ) 1862 1863 request = zoo.animals().insert(media_body=media_upload, body=None) 1864 1865 # 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 ) 1872 1873 try: 1874 body = request.execute(http=http) 1875 except HttpError as e: 1876 self.assertEqual(b"01234", e.content) 1877 1878 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 ) 1885 1886 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1887 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1888 1889 # Create an upload that doesn't know the full size of the media. 1890 class IoBaseUnknownLength(MediaUpload): 1891 def chunksize(self): 1892 return 10 1893 1894 def mimetype(self): 1895 return "image/png" 1896 1897 def size(self): 1898 return None 1899 1900 def resumable(self): 1901 return True 1902 1903 def getbytes(self, begin, length): 1904 return "0123456789" 1905 1906 upload = IoBaseUnknownLength() 1907 1908 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"}) 1911 1912 def test_resumable_media_no_streaming_on_unsupported_platforms(self): 1913 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1914 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1915 1916 class IoBaseHasStream(MediaUpload): 1917 def chunksize(self): 1918 return 10 1919 1920 def mimetype(self): 1921 return "image/png" 1922 1923 def size(self): 1924 return None 1925 1926 def resumable(self): 1927 return True 1928 1929 def getbytes(self, begin, length): 1930 return "0123456789" 1931 1932 def has_stream(self): 1933 return True 1934 1935 def stream(self): 1936 raise NotImplementedError() 1937 1938 upload = IoBaseHasStream() 1939 1940 orig_version = sys.version_info 1941 1942 sys.version_info = (2, 6, 5, "final", 0) 1943 1944 request = zoo.animals().insert(media_body=upload, body=None) 1945 1946 # 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 ) 1953 1954 self.assertRaises(NotImplementedError, request.next_chunk, http=http) 1955 1956 sys.version_info = orig_version 1957 1958 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 ) 1965 1966 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1967 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1968 1969 fd = io.BytesIO(b"data goes here") 1970 1971 # 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 ) 1975 1976 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 ) 1981 1982 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 ) 1989 1990 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 1991 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 1992 1993 # Create an upload that doesn't know the full size of the media. 1994 fd = io.BytesIO(b"data goes here") 1995 1996 upload = MediaIoBaseUpload( 1997 fd=fd, mimetype="image/png", chunksize=500, resumable=True 1998 ) 1999 2000 request = zoo.animals().insert(media_body=upload, body=None) 2001 2002 # Put it in an error state. 2003 self.assertRaises(HttpError, request.next_chunk, http=http) 2004 2005 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 ) 2018 2019 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 ] 2039 2040 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 2041 zoo = build("zoo", "v1", http=http, static_discovery=False) 2042 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys) 2043 2044 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)) 2061 2062 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 2070 2071 def _dummy_zoo_request(self): 2072 zoo_contents = read_datafile("zoo.json") 2073 2074 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 ) 2079 2080 http = build_http() 2081 original_request = http.request 2082 2083 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) 2087 2088 http.request = wrapped_request 2089 return http 2090 2091 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 ) 2107 2108 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")) 2113 2114 zoo = build("zoo", "v1", http=http, static_discovery=False) 2115 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")) 2120 2121 def test_resumable_media_upload_no_content(self): 2122 self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) 2123 zoo = build("zoo", "v1", http=self.http, static_discovery=False) 2124 2125 media_upload = MediaFileUpload(datafile("empty"), resumable=True) 2126 request = zoo.animals().insert(media_body=media_upload, body=None) 2127 2128 self.assertEqual(media_upload, request.resumable) 2129 self.assertEqual(request.body, None) 2130 self.assertEqual(request.resumable_uri, None) 2131 2132 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 ) 2145 2146 status, body = request.next_chunk(http=http) 2147 self.assertEqual(None, body) 2148 self.assertTrue(isinstance(status, MediaUploadProgress)) 2149 self.assertEqual(0, status.progress()) 2150 2151 2152class Next(unittest.TestCase): 2153 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, {})) 2158 2159 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) 2165 2166 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"}) 2171 parsed = urllib.parse.urlparse(next_request.uri) 2172 q = urllib.parse.parse_qs(parsed.query) 2173 self.assertEqual(q["pageToken"][0], "123abc") 2174 2175 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"}) 2180 parsed = urllib.parse.urlparse(next_request.uri) 2181 q = urllib.parse.parse_qs(parsed.query) 2182 self.assertEqual(q["pageToken"][0], "123abc") 2183 2184 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") 2191 # 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)) 2194 2195 def test_next_with_method_with_no_properties(self): 2196 self.http = HttpMock(datafile("latitude.json"), {"status": "200"}) 2197 service = build("latitude", "v1", http=self.http, static_discovery=False) 2198 service.currentLocation().get() 2199 2200 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))) 2205 2206 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"}) 2211 parsed = urllib.parse.urlparse(next_request.uri) 2212 q = urllib.parse.parse_qs(parsed.query) 2213 self.assertEqual(q["pageToken"][0], "123abc") 2214 2215 2216class MediaGet(unittest.TestCase): 2217 def test_get_media(self): 2218 http = HttpMock(datafile("zoo.json"), {"status": "200"}) 2219 zoo = build("zoo", "v1", http=http, static_discovery=False) 2220 request = zoo.animals().get_media(name="Lion") 2221 2222 parsed = urllib.parse.urlparse(request.uri) 2223 q = urllib.parse.parse_qs(parsed.query) 2224 self.assertEqual(q["alt"], ["media"]) 2225 self.assertEqual(request.headers["accept"], "*/*") 2226 2227 http = HttpMockSequence([({"status": "200"}, "standing in for media")]) 2228 response = request.execute(http=http) 2229 self.assertEqual(b"standing in for media", response) 2230 2231 2232if __name__ == "__main__": 2233 unittest.main() 2234