Home | History | Annotate | Download | only in tests
      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 
     21 Unit tests for objects created from discovery documents.
     22 """
     23 from __future__ import absolute_import
     24 import six
     25 
     26 __author__ = 'jcgregorio (at] google.com (Joe Gregorio)'
     27 
     28 from six import BytesIO, StringIO
     29 from six.moves.urllib.parse import urlparse, parse_qs
     30 
     31 import copy
     32 import datetime
     33 import httplib2
     34 import itertools
     35 import json
     36 import os
     37 import pickle
     38 import re
     39 import sys
     40 import unittest2 as unittest
     41 
     42 import mock
     43 
     44 import google.auth.credentials
     45 import google_auth_httplib2
     46 from googleapiclient.discovery import _fix_up_media_upload
     47 from googleapiclient.discovery import _fix_up_method_description
     48 from googleapiclient.discovery import _fix_up_parameters
     49 from googleapiclient.discovery import _urljoin
     50 from googleapiclient.discovery import build
     51 from googleapiclient.discovery import build_from_document
     52 from googleapiclient.discovery import DISCOVERY_URI
     53 from googleapiclient.discovery import key2param
     54 from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
     55 from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE
     56 from googleapiclient.discovery import ResourceMethodParameters
     57 from googleapiclient.discovery import STACK_QUERY_PARAMETERS
     58 from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
     59 from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE
     60 from googleapiclient.discovery_cache.base import Cache
     61 from googleapiclient.errors import HttpError
     62 from googleapiclient.errors import InvalidJsonError
     63 from googleapiclient.errors import MediaUploadSizeError
     64 from googleapiclient.errors import ResumableUploadError
     65 from googleapiclient.errors import UnacceptableMimeTypeError
     66 from googleapiclient.errors import UnknownApiNameOrVersion
     67 from googleapiclient.errors import UnknownFileType
     68 from googleapiclient.http import build_http
     69 from googleapiclient.http import BatchHttpRequest
     70 from googleapiclient.http import HttpMock
     71 from googleapiclient.http import HttpMockSequence
     72 from googleapiclient.http import MediaFileUpload
     73 from googleapiclient.http import MediaIoBaseUpload
     74 from googleapiclient.http import MediaUpload
     75 from googleapiclient.http import MediaUploadProgress
     76 from googleapiclient.http import tunnel_patch
     77 from googleapiclient.model import JsonModel
     78 from googleapiclient.schema import Schemas
     79 from oauth2client import GOOGLE_TOKEN_URI
     80 from oauth2client.client import OAuth2Credentials, GoogleCredentials
     81 
     82 from googleapiclient import _helpers as util
     83 
     84 import uritemplate
     85 
     86 
     87 DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
     88 
     89 
     90 def assertUrisEqual(testcase, expected, actual):
     91   """Test that URIs are the same, up to reordering of query parameters."""
     92   expected = urlparse(expected)
     93   actual = urlparse(actual)
     94   testcase.assertEqual(expected.scheme, actual.scheme)
     95   testcase.assertEqual(expected.netloc, actual.netloc)
     96   testcase.assertEqual(expected.path, actual.path)
     97   testcase.assertEqual(expected.params, actual.params)
     98   testcase.assertEqual(expected.fragment, actual.fragment)
     99   expected_query = parse_qs(expected.query)
    100   actual_query = parse_qs(actual.query)
    101   for name in list(expected_query.keys()):
    102     testcase.assertEqual(expected_query[name], actual_query[name])
    103   for name in list(actual_query.keys()):
    104     testcase.assertEqual(expected_query[name], actual_query[name])
    105 
    106 
    107 def datafile(filename):
    108   return os.path.join(DATA_DIR, filename)
    109 
    110 
    111 class SetupHttplib2(unittest.TestCase):
    112 
    113   def test_retries(self):
    114     # Merely loading googleapiclient.discovery should set the RETRIES to 1.
    115     self.assertEqual(1, httplib2.RETRIES)
    116 
    117 
    118 class Utilities(unittest.TestCase):
    119 
    120   def setUp(self):
    121     with open(datafile('zoo.json'), 'r') as fh:
    122       self.zoo_root_desc = json.loads(fh.read())
    123     self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
    124     self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
    125     self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
    126     self.zoo_schema = Schemas(self.zoo_root_desc)
    127 
    128   def test_key2param(self):
    129     self.assertEqual('max_results', key2param('max-results'))
    130     self.assertEqual('x007_bond', key2param('007-bond'))
    131 
    132   def _base_fix_up_parameters_test(
    133           self, method_desc, http_method, root_desc, schema):
    134     self.assertEqual(method_desc['httpMethod'], http_method)
    135 
    136     method_desc_copy = copy.deepcopy(method_desc)
    137     self.assertEqual(method_desc, method_desc_copy)
    138 
    139     parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method,
    140                                     schema)
    141 
    142     self.assertNotEqual(method_desc, method_desc_copy)
    143 
    144     for param_name in STACK_QUERY_PARAMETERS:
    145       self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
    146                        parameters[param_name])
    147 
    148     for param_name, value in six.iteritems(root_desc.get('parameters', {})):
    149       self.assertEqual(value, parameters[param_name])
    150 
    151     return parameters
    152 
    153   def test_fix_up_parameters_get(self):
    154     parameters = self._base_fix_up_parameters_test(
    155       self.zoo_get_method_desc, 'GET', self.zoo_root_desc, self.zoo_schema)
    156     # Since http_method is 'GET'
    157     self.assertFalse('body' in parameters)
    158 
    159   def test_fix_up_parameters_insert(self):
    160     parameters = self._base_fix_up_parameters_test(
    161       self.zoo_insert_method_desc, 'POST', self.zoo_root_desc, self.zoo_schema)
    162     body = {
    163         'description': 'The request body.',
    164         'type': 'object',
    165         'required': True,
    166         '$ref': 'Animal',
    167     }
    168     self.assertEqual(parameters['body'], body)
    169 
    170   def test_fix_up_parameters_check_body(self):
    171     dummy_root_desc = {}
    172     dummy_schema = {
    173       'Request': {
    174         'properties': {
    175           "description": "Required. Dummy parameter.",
    176           "type": "string"
    177         }
    178       }
    179     }
    180     no_payload_http_method = 'DELETE'
    181     with_payload_http_method = 'PUT'
    182 
    183     invalid_method_desc = {'response': 'Who cares'}
    184     valid_method_desc = {
    185       'request': {
    186         'key1': 'value1',
    187         'key2': 'value2',
    188         '$ref': 'Request'
    189       }
    190     }
    191 
    192     parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
    193                                     no_payload_http_method, dummy_schema)
    194     self.assertFalse('body' in parameters)
    195 
    196     parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
    197                                     no_payload_http_method, dummy_schema)
    198     self.assertFalse('body' in parameters)
    199 
    200     parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
    201                                     with_payload_http_method, dummy_schema)
    202     self.assertFalse('body' in parameters)
    203 
    204     parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
    205                                     with_payload_http_method, dummy_schema)
    206     body = {
    207         'description': 'The request body.',
    208         'type': 'object',
    209         'required': True,
    210         '$ref': 'Request',
    211         'key1': 'value1',
    212         'key2': 'value2',
    213     }
    214     self.assertEqual(parameters['body'], body)
    215 
    216   def test_fix_up_parameters_optional_body(self):
    217     # Request with no parameters
    218     dummy_schema = {'Request': {'properties': {}}}
    219     method_desc = {'request': {'$ref': 'Request'}}
    220 
    221     parameters = _fix_up_parameters(method_desc, {}, 'POST', dummy_schema)
    222     self.assertFalse(parameters['body']['required'])
    223 
    224   def _base_fix_up_method_description_test(
    225       self, method_desc, initial_parameters, final_parameters,
    226       final_accept, final_max_size, final_media_path_url):
    227     fake_root_desc = {'rootUrl': 'http://root/',
    228                       'servicePath': 'fake/'}
    229     fake_path_url = 'fake-path/'
    230 
    231     accept, max_size, media_path_url = _fix_up_media_upload(
    232         method_desc, fake_root_desc, fake_path_url, initial_parameters)
    233     self.assertEqual(accept, final_accept)
    234     self.assertEqual(max_size, final_max_size)
    235     self.assertEqual(media_path_url, final_media_path_url)
    236     self.assertEqual(initial_parameters, final_parameters)
    237 
    238   def test_fix_up_media_upload_no_initial_invalid(self):
    239     invalid_method_desc = {'response': 'Who cares'}
    240     self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
    241                                               [], 0, None)
    242 
    243   def test_fix_up_media_upload_no_initial_valid_minimal(self):
    244     valid_method_desc = {'mediaUpload': {'accept': []}}
    245     final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
    246                         'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
    247     self._base_fix_up_method_description_test(
    248         valid_method_desc, {}, final_parameters, [], 0,
    249         'http://root/upload/fake/fake-path/')
    250 
    251   def test_fix_up_media_upload_no_initial_valid_full(self):
    252     valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
    253     final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
    254                         'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
    255     ten_gb = 10 * 2**30
    256     self._base_fix_up_method_description_test(
    257         valid_method_desc, {}, final_parameters, ['*/*'],
    258         ten_gb, 'http://root/upload/fake/fake-path/')
    259 
    260   def test_fix_up_media_upload_with_initial_invalid(self):
    261     invalid_method_desc = {'response': 'Who cares'}
    262     initial_parameters = {'body': {}}
    263     self._base_fix_up_method_description_test(
    264         invalid_method_desc, initial_parameters,
    265         initial_parameters, [], 0, None)
    266 
    267   def test_fix_up_media_upload_with_initial_valid_minimal(self):
    268     valid_method_desc = {'mediaUpload': {'accept': []}}
    269     initial_parameters = {'body': {}}
    270     final_parameters = {'body': {'required': False},
    271                         'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
    272                         'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
    273     self._base_fix_up_method_description_test(
    274         valid_method_desc, initial_parameters, final_parameters, [], 0,
    275         'http://root/upload/fake/fake-path/')
    276 
    277   def test_fix_up_media_upload_with_initial_valid_full(self):
    278     valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
    279     initial_parameters = {'body': {}}
    280     final_parameters = {'body': {'required': False},
    281                         'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
    282                         'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
    283     ten_gb = 10 * 2**30
    284     self._base_fix_up_method_description_test(
    285         valid_method_desc, initial_parameters, final_parameters, ['*/*'],
    286         ten_gb, 'http://root/upload/fake/fake-path/')
    287 
    288   def test_fix_up_method_description_get(self):
    289     result = _fix_up_method_description(self.zoo_get_method_desc,
    290                                         self.zoo_root_desc, self.zoo_schema)
    291     path_url = 'query'
    292     http_method = 'GET'
    293     method_id = 'bigquery.query'
    294     accept = []
    295     max_size = 0
    296     media_path_url = None
    297     self.assertEqual(result, (path_url, http_method, method_id, accept,
    298                               max_size, media_path_url))
    299 
    300   def test_fix_up_method_description_insert(self):
    301     result = _fix_up_method_description(self.zoo_insert_method_desc,
    302                                         self.zoo_root_desc, self.zoo_schema)
    303     path_url = 'animals'
    304     http_method = 'POST'
    305     method_id = 'zoo.animals.insert'
    306     accept = ['image/png']
    307     max_size = 1024
    308     media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
    309     self.assertEqual(result, (path_url, http_method, method_id, accept,
    310                               max_size, media_path_url))
    311 
    312   def test_urljoin(self):
    313     # We want to exhaustively test various URL combinations.
    314     simple_bases = ['https://www.googleapis.com', 'https://www.googleapis.com/']
    315     long_urls = ['foo/v1/bar:custom?alt=json', '/foo/v1/bar:custom?alt=json']
    316 
    317     long_bases = [
    318       'https://www.googleapis.com/foo/v1',
    319       'https://www.googleapis.com/foo/v1/',
    320     ]
    321     simple_urls = ['bar:custom?alt=json', '/bar:custom?alt=json']
    322 
    323     final_url = 'https://www.googleapis.com/foo/v1/bar:custom?alt=json'
    324     for base, url in itertools.product(simple_bases, long_urls):
    325       self.assertEqual(final_url, _urljoin(base, url))
    326     for base, url in itertools.product(long_bases, simple_urls):
    327       self.assertEqual(final_url, _urljoin(base, url))
    328 
    329 
    330   def test_ResourceMethodParameters_zoo_get(self):
    331     parameters = ResourceMethodParameters(self.zoo_get_method_desc)
    332 
    333     param_types = {'a': 'any',
    334                    'b': 'boolean',
    335                    'e': 'string',
    336                    'er': 'string',
    337                    'i': 'integer',
    338                    'n': 'number',
    339                    'o': 'object',
    340                    'q': 'string',
    341                    'rr': 'string'}
    342     keys = list(param_types.keys())
    343     self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
    344     self.assertEqual(parameters.required_params, [])
    345     self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
    346     self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
    347     self.assertEqual(sorted(parameters.query_params),
    348                      ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
    349     self.assertEqual(parameters.path_params, set())
    350     self.assertEqual(parameters.param_types, param_types)
    351     enum_params = {'e': ['foo', 'bar'],
    352                    'er': ['one', 'two', 'three']}
    353     self.assertEqual(parameters.enum_params, enum_params)
    354 
    355   def test_ResourceMethodParameters_zoo_animals_patch(self):
    356     method_desc = self.zoo_animals_resource['methods']['patch']
    357     parameters = ResourceMethodParameters(method_desc)
    358 
    359     param_types = {'name': 'string'}
    360     keys = list(param_types.keys())
    361     self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
    362     self.assertEqual(parameters.required_params, ['name'])
    363     self.assertEqual(parameters.repeated_params, [])
    364     self.assertEqual(parameters.pattern_params, {})
    365     self.assertEqual(parameters.query_params, [])
    366     self.assertEqual(parameters.path_params, set(['name']))
    367     self.assertEqual(parameters.param_types, param_types)
    368     self.assertEqual(parameters.enum_params, {})
    369 
    370 
    371 class DiscoveryErrors(unittest.TestCase):
    372 
    373   def test_tests_should_be_run_with_strict_positional_enforcement(self):
    374     try:
    375       plus = build('plus', 'v1', None)
    376       self.fail("should have raised a TypeError exception over missing http=.")
    377     except TypeError:
    378       pass
    379 
    380   def test_failed_to_parse_discovery_json(self):
    381     self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
    382     try:
    383       plus = build('plus', 'v1', http=self.http, cache_discovery=False)
    384       self.fail("should have raised an exception over malformed JSON.")
    385     except InvalidJsonError:
    386       pass
    387 
    388   def test_unknown_api_name_or_version(self):
    389       http = HttpMockSequence([
    390         ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
    391         ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
    392       ])
    393       with self.assertRaises(UnknownApiNameOrVersion):
    394         plus = build('plus', 'v1', http=http, cache_discovery=False)
    395 
    396   def test_credentials_and_http_mutually_exclusive(self):
    397     http = HttpMock(datafile('plus.json'), {'status': '200'})
    398     with self.assertRaises(ValueError):
    399       build(
    400         'plus', 'v1', http=http, credentials=mock.sentinel.credentials)
    401 
    402 
    403 class DiscoveryFromDocument(unittest.TestCase):
    404   MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
    405 
    406   def test_can_build_from_local_document(self):
    407     discovery = open(datafile('plus.json')).read()
    408     plus = build_from_document(
    409       discovery, base="https://www.googleapis.com/",
    410       credentials=self.MOCK_CREDENTIALS)
    411     self.assertTrue(plus is not None)
    412     self.assertTrue(hasattr(plus, 'activities'))
    413 
    414   def test_can_build_from_local_deserialized_document(self):
    415     discovery = open(datafile('plus.json')).read()
    416     discovery = json.loads(discovery)
    417     plus = build_from_document(
    418       discovery, base="https://www.googleapis.com/",
    419       credentials=self.MOCK_CREDENTIALS)
    420     self.assertTrue(plus is not None)
    421     self.assertTrue(hasattr(plus, 'activities'))
    422 
    423   def test_building_with_base_remembers_base(self):
    424     discovery = open(datafile('plus.json')).read()
    425 
    426     base = "https://www.example.com/"
    427     plus = build_from_document(
    428       discovery, base=base, credentials=self.MOCK_CREDENTIALS)
    429     self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
    430 
    431   def test_building_with_optional_http_with_authorization(self):
    432     discovery = open(datafile('plus.json')).read()
    433     plus = build_from_document(
    434       discovery, base="https://www.googleapis.com/",
    435       credentials=self.MOCK_CREDENTIALS)
    436 
    437     # plus service requires Authorization, hence we expect to see AuthorizedHttp object here
    438     self.assertIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
    439     self.assertIsInstance(plus._http.http, httplib2.Http)
    440     self.assertIsInstance(plus._http.http.timeout, int)
    441     self.assertGreater(plus._http.http.timeout, 0)
    442 
    443   def test_building_with_optional_http_with_no_authorization(self):
    444     discovery = open(datafile('plus.json')).read()
    445     # Cleanup auth field, so we would use plain http client
    446     discovery = json.loads(discovery)
    447     discovery['auth'] = {}
    448     discovery = json.dumps(discovery)
    449 
    450     plus = build_from_document(
    451       discovery, base="https://www.googleapis.com/",
    452       credentials=None)
    453     # plus service requires Authorization
    454     self.assertIsInstance(plus._http, httplib2.Http)
    455     self.assertIsInstance(plus._http.timeout, int)
    456     self.assertGreater(plus._http.timeout, 0)
    457 
    458   def test_building_with_explicit_http(self):
    459     http = HttpMock()
    460     discovery = open(datafile('plus.json')).read()
    461     plus = build_from_document(
    462       discovery, base="https://www.googleapis.com/", http=http)
    463     self.assertEquals(plus._http, http)
    464 
    465   def test_building_with_developer_key_skips_adc(self):
    466     discovery = open(datafile('plus.json')).read()
    467     plus = build_from_document(
    468       discovery, base="https://www.googleapis.com/", developerKey='123')
    469     self.assertIsInstance(plus._http, httplib2.Http)
    470     # It should not be an AuthorizedHttp, because that would indicate that
    471     # application default credentials were used.
    472     self.assertNotIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
    473 
    474 
    475 class DiscoveryFromHttp(unittest.TestCase):
    476   def setUp(self):
    477     self.old_environ = os.environ.copy()
    478 
    479   def tearDown(self):
    480     os.environ = self.old_environ
    481 
    482   def test_userip_is_added_to_discovery_uri(self):
    483     # build() will raise an HttpError on a 400, use this to pick the request uri
    484     # out of the raised exception.
    485     os.environ['REMOTE_ADDR'] = '10.0.0.1'
    486     try:
    487       http = HttpMockSequence([
    488         ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
    489         ])
    490       zoo = build('zoo', 'v1', http=http, developerKey=None,
    491                   discoveryServiceUrl='http://example.com')
    492       self.fail('Should have raised an exception.')
    493     except HttpError as e:
    494       self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
    495 
    496   def test_userip_missing_is_not_added_to_discovery_uri(self):
    497     # build() will raise an HttpError on a 400, use this to pick the request uri
    498     # out of the raised exception.
    499     try:
    500       http = HttpMockSequence([
    501         ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
    502         ])
    503       zoo = build('zoo', 'v1', http=http, developerKey=None,
    504                   discoveryServiceUrl='http://example.com')
    505       self.fail('Should have raised an exception.')
    506     except HttpError as e:
    507       self.assertEqual(e.uri, 'http://example.com')
    508 
    509   def test_key_is_added_to_discovery_uri(self):
    510     # build() will raise an HttpError on a 400, use this to pick the request uri
    511     # out of the raised exception.
    512     try:
    513       http = HttpMockSequence([
    514         ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
    515         ])
    516       zoo = build('zoo', 'v1', http=http, developerKey='foo',
    517                   discoveryServiceUrl='http://example.com')
    518       self.fail('Should have raised an exception.')
    519     except HttpError as e:
    520       self.assertEqual(e.uri, 'http://example.com?key=foo')
    521 
    522   def test_discovery_loading_from_v2_discovery_uri(self):
    523       http = HttpMockSequence([
    524         ({'status': '404'}, 'Not found'),
    525         ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
    526       ])
    527       zoo = build('zoo', 'v1', http=http, cache_discovery=False)
    528       self.assertTrue(hasattr(zoo, 'animals'))
    529 
    530 class DiscoveryFromAppEngineCache(unittest.TestCase):
    531   def test_appengine_memcache(self):
    532     # Hack module import
    533     self.orig_import = __import__
    534     self.mocked_api = mock.MagicMock()
    535 
    536     def import_mock(name, *args, **kwargs):
    537       if name == 'google.appengine.api':
    538         return self.mocked_api
    539       return self.orig_import(name, *args, **kwargs)
    540 
    541     import_fullname = '__builtin__.__import__'
    542     if sys.version_info[0] >= 3:
    543       import_fullname = 'builtins.__import__'
    544 
    545     with mock.patch(import_fullname, side_effect=import_mock):
    546       namespace = 'google-api-client'
    547       self.http = HttpMock(datafile('plus.json'), {'status': '200'})
    548 
    549       self.mocked_api.memcache.get.return_value = None
    550 
    551       plus = build('plus', 'v1', http=self.http)
    552 
    553       # memcache.get is called once
    554       url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
    555       self.mocked_api.memcache.get.assert_called_once_with(url,
    556                                                            namespace=namespace)
    557 
    558       # memcache.set is called once
    559       with open(datafile('plus.json')) as f:
    560         content = f.read()
    561       self.mocked_api.memcache.set.assert_called_once_with(
    562         url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace)
    563 
    564       # Returns the cached content this time.
    565       self.mocked_api.memcache.get.return_value = content
    566 
    567       # Make sure the contents are returned from the cache.
    568       # (Otherwise it should through an error)
    569       self.http = HttpMock(None, {'status': '200'})
    570 
    571       plus = build('plus', 'v1', http=self.http)
    572 
    573       # memcache.get is called twice
    574       self.mocked_api.memcache.get.assert_has_calls(
    575         [mock.call(url, namespace=namespace),
    576          mock.call(url, namespace=namespace)])
    577 
    578       # memcahce.set is called just once
    579       self.mocked_api.memcache.set.assert_called_once_with(
    580         url, content, time=DISCOVERY_DOC_MAX_AGE,namespace=namespace)
    581 
    582 
    583 class DictCache(Cache):
    584   def __init__(self):
    585     self.d = {}
    586   def get(self, url):
    587     return self.d.get(url, None)
    588   def set(self, url, content):
    589     self.d[url] = content
    590   def contains(self, url):
    591     return url in self.d
    592 
    593 
    594 class DiscoveryFromFileCache(unittest.TestCase):
    595   def test_file_based_cache(self):
    596     cache = mock.Mock(wraps=DictCache())
    597     with mock.patch('googleapiclient.discovery_cache.autodetect',
    598                     return_value=cache):
    599       self.http = HttpMock(datafile('plus.json'), {'status': '200'})
    600 
    601       plus = build('plus', 'v1', http=self.http)
    602 
    603       # cache.get is called once
    604       url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
    605       cache.get.assert_called_once_with(url)
    606 
    607       # cache.set is called once
    608       with open(datafile('plus.json')) as f:
    609         content = f.read()
    610       cache.set.assert_called_once_with(url, content)
    611 
    612       # Make sure there is a cache entry for the plus v1 discovery doc.
    613       self.assertTrue(cache.contains(url))
    614 
    615       # Make sure the contents are returned from the cache.
    616       # (Otherwise it should through an error)
    617       self.http = HttpMock(None, {'status': '200'})
    618 
    619       plus = build('plus', 'v1', http=self.http)
    620 
    621       # cache.get is called twice
    622       cache.get.assert_has_calls([mock.call(url), mock.call(url)])
    623 
    624       # cahce.set is called just once
    625       cache.set.assert_called_once_with(url, content)
    626 
    627 
    628 class Discovery(unittest.TestCase):
    629 
    630   def test_method_error_checking(self):
    631     self.http = HttpMock(datafile('plus.json'), {'status': '200'})
    632     plus = build('plus', 'v1', http=self.http)
    633 
    634     # Missing required parameters
    635     try:
    636       plus.activities().list()
    637       self.fail()
    638     except TypeError as e:
    639       self.assertTrue('Missing' in str(e))
    640 
    641     # Missing required parameters even if supplied as None.
    642     try:
    643       plus.activities().list(collection=None, userId=None)
    644       self.fail()
    645     except TypeError as e:
    646       self.assertTrue('Missing' in str(e))
    647 
    648     # Parameter doesn't match regex
    649     try:
    650       plus.activities().list(collection='not_a_collection_name', userId='me')
    651       self.fail()
    652     except TypeError as e:
    653       self.assertTrue('not an allowed value' in str(e))
    654 
    655     # Unexpected parameter
    656     try:
    657       plus.activities().list(flubber=12)
    658       self.fail()
    659     except TypeError as e:
    660       self.assertTrue('unexpected' in str(e))
    661 
    662   def _check_query_types(self, request):
    663     parsed = urlparse(request.uri)
    664     q = parse_qs(parsed[4])
    665     self.assertEqual(q['q'], ['foo'])
    666     self.assertEqual(q['i'], ['1'])
    667     self.assertEqual(q['n'], ['1.0'])
    668     self.assertEqual(q['b'], ['false'])
    669     self.assertEqual(q['a'], ['[1, 2, 3]'])
    670     self.assertEqual(q['o'], ['{\'a\': 1}'])
    671     self.assertEqual(q['e'], ['bar'])
    672 
    673   def test_type_coercion(self):
    674     http = HttpMock(datafile('zoo.json'), {'status': '200'})
    675     zoo = build('zoo', 'v1', http=http)
    676 
    677     request = zoo.query(
    678         q="foo", i=1.0, n=1.0, b=0, a=[1,2,3], o={'a':1}, e='bar')
    679     self._check_query_types(request)
    680     request = zoo.query(
    681         q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
    682     self._check_query_types(request)
    683 
    684     request = zoo.query(
    685         q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar', er='two')
    686 
    687     request = zoo.query(
    688         q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
    689         er=['one', 'three'], rr=['foo', 'bar'])
    690     self._check_query_types(request)
    691 
    692     # Five is right out.
    693     self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
    694 
    695   def test_optional_stack_query_parameters(self):
    696     http = HttpMock(datafile('zoo.json'), {'status': '200'})
    697     zoo = build('zoo', 'v1', http=http)
    698     request = zoo.query(trace='html', fields='description')
    699 
    700     parsed = urlparse(request.uri)
    701     q = parse_qs(parsed[4])
    702     self.assertEqual(q['trace'], ['html'])
    703     self.assertEqual(q['fields'], ['description'])
    704 
    705   def test_string_params_value_of_none_get_dropped(self):
    706     http = HttpMock(datafile('zoo.json'), {'status': '200'})
    707     zoo = build('zoo', 'v1', http=http)
    708     request = zoo.query(trace=None, fields='description')
    709 
    710     parsed = urlparse(request.uri)
    711     q = parse_qs(parsed[4])
    712     self.assertFalse('trace' in q)
    713 
    714   def test_model_added_query_parameters(self):
    715     http = HttpMock(datafile('zoo.json'), {'status': '200'})
    716     zoo = build('zoo', 'v1', http=http)
    717     request = zoo.animals().get(name='Lion')
    718 
    719     parsed = urlparse(request.uri)
    720     q = parse_qs(parsed[4])
    721     self.assertEqual(q['alt'], ['json'])
    722     self.assertEqual(request.headers['accept'], 'application/json')
    723 
    724   def test_fallback_to_raw_model(self):
    725     http = HttpMock(datafile('zoo.json'), {'status': '200'})
    726     zoo = build('zoo', 'v1', http=http)
    727     request = zoo.animals().getmedia(name='Lion')
    728 
    729     parsed = urlparse(request.uri)
    730     q = parse_qs(parsed[4])
    731     self.assertTrue('alt' not in q)
    732     self.assertEqual(request.headers['accept'], '*/*')
    733 
    734   def test_patch(self):
    735     http = HttpMock(datafile('zoo.json'), {'status': '200'})
    736     zoo = build('zoo', 'v1', http=http)
    737     request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
    738 
    739     self.assertEqual(request.method, 'PATCH')
    740 
    741   def test_batch_request_from_discovery(self):
    742     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    743     # zoo defines a batchPath
    744     zoo = build('zoo', 'v1', http=self.http)
    745     batch_request = zoo.new_batch_http_request()
    746     self.assertEqual(batch_request._batch_uri,
    747                      "https://www.googleapis.com/batchZoo")
    748 
    749   def test_batch_request_from_default(self):
    750     self.http = HttpMock(datafile('plus.json'), {'status': '200'})
    751     # plus does not define a batchPath
    752     plus = build('plus', 'v1', http=self.http)
    753     batch_request = plus.new_batch_http_request()
    754     self.assertEqual(batch_request._batch_uri,
    755                      "https://www.googleapis.com/batch")
    756 
    757   def test_tunnel_patch(self):
    758     http = HttpMockSequence([
    759       ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
    760       ({'status': '200'}, 'echo_request_headers_as_json'),
    761       ])
    762     http = tunnel_patch(http)
    763     zoo = build('zoo', 'v1', http=http, cache_discovery=False)
    764     resp = zoo.animals().patch(
    765         name='lion', body='{"description": "foo"}').execute()
    766 
    767     self.assertTrue('x-http-method-override' in resp)
    768 
    769   def test_plus_resources(self):
    770     self.http = HttpMock(datafile('plus.json'), {'status': '200'})
    771     plus = build('plus', 'v1', http=self.http)
    772     self.assertTrue(getattr(plus, 'activities'))
    773     self.assertTrue(getattr(plus, 'people'))
    774 
    775   def test_oauth2client_credentials(self):
    776     credentials = mock.Mock(spec=GoogleCredentials)
    777     credentials.create_scoped_required.return_value = False
    778 
    779     discovery = open(datafile('plus.json')).read()
    780     service = build_from_document(discovery, credentials=credentials)
    781     self.assertEqual(service._http, credentials.authorize.return_value)
    782 
    783   def test_google_auth_credentials(self):
    784     credentials = mock.Mock(spec=google.auth.credentials.Credentials)
    785     discovery = open(datafile('plus.json')).read()
    786     service = build_from_document(discovery, credentials=credentials)
    787 
    788     self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp)
    789     self.assertEqual(service._http.credentials, credentials)
    790 
    791   def test_no_scopes_no_credentials(self):
    792     # Zoo doesn't have scopes
    793     discovery = open(datafile('zoo.json')).read()
    794     service = build_from_document(discovery)
    795     # Should be an ordinary httplib2.Http instance and not AuthorizedHttp.
    796     self.assertIsInstance(service._http, httplib2.Http)
    797 
    798   def test_full_featured(self):
    799     # Zoo should exercise all discovery facets
    800     # and should also have no future.json file.
    801     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    802     zoo = build('zoo', 'v1', http=self.http)
    803     self.assertTrue(getattr(zoo, 'animals'))
    804 
    805     request = zoo.animals().list(name='bat', projection="full")
    806     parsed = urlparse(request.uri)
    807     q = parse_qs(parsed[4])
    808     self.assertEqual(q['name'], ['bat'])
    809     self.assertEqual(q['projection'], ['full'])
    810 
    811   def test_nested_resources(self):
    812     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    813     zoo = build('zoo', 'v1', http=self.http)
    814     self.assertTrue(getattr(zoo, 'animals'))
    815     request = zoo.my().favorites().list(max_results="5")
    816     parsed = urlparse(request.uri)
    817     q = parse_qs(parsed[4])
    818     self.assertEqual(q['max-results'], ['5'])
    819 
    820   @unittest.skipIf(six.PY3, 'print is not a reserved name in Python 3')
    821   def test_methods_with_reserved_names(self):
    822     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    823     zoo = build('zoo', 'v1', http=self.http)
    824     self.assertTrue(getattr(zoo, 'animals'))
    825     request = zoo.global_().print_().assert_(max_results="5")
    826     parsed = urlparse(request.uri)
    827     self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
    828 
    829   def test_top_level_functions(self):
    830     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    831     zoo = build('zoo', 'v1', http=self.http)
    832     self.assertTrue(getattr(zoo, 'query'))
    833     request = zoo.query(q="foo")
    834     parsed = urlparse(request.uri)
    835     q = parse_qs(parsed[4])
    836     self.assertEqual(q['q'], ['foo'])
    837 
    838   def test_simple_media_uploads(self):
    839     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    840     zoo = build('zoo', 'v1', http=self.http)
    841     doc = getattr(zoo.animals().insert, '__doc__')
    842     self.assertTrue('media_body' in doc)
    843 
    844   def test_simple_media_upload_no_max_size_provided(self):
    845     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    846     zoo = build('zoo', 'v1', http=self.http)
    847     request = zoo.animals().crossbreed(media_body=datafile('small.png'))
    848     self.assertEquals('image/png', request.headers['content-type'])
    849     self.assertEquals(b'PNG', request.body[1:4])
    850 
    851   def test_simple_media_raise_correct_exceptions(self):
    852     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    853     zoo = build('zoo', 'v1', http=self.http)
    854 
    855     try:
    856       zoo.animals().insert(media_body=datafile('smiley.png'))
    857       self.fail("should throw exception if media is too large.")
    858     except MediaUploadSizeError:
    859       pass
    860 
    861     try:
    862       zoo.animals().insert(media_body=datafile('small.jpg'))
    863       self.fail("should throw exception if mimetype is unacceptable.")
    864     except UnacceptableMimeTypeError:
    865       pass
    866 
    867   def test_simple_media_good_upload(self):
    868     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    869     zoo = build('zoo', 'v1', http=self.http)
    870 
    871     request = zoo.animals().insert(media_body=datafile('small.png'))
    872     self.assertEquals('image/png', request.headers['content-type'])
    873     self.assertEquals(b'PNG', request.body[1:4])
    874     assertUrisEqual(self,
    875         'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
    876         request.uri)
    877 
    878   def test_simple_media_unknown_mimetype(self):
    879     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    880     zoo = build('zoo', 'v1', http=self.http)
    881 
    882     try:
    883       zoo.animals().insert(media_body=datafile('small-png'))
    884       self.fail("should throw exception if mimetype is unknown.")
    885     except UnknownFileType:
    886       pass
    887 
    888     request = zoo.animals().insert(media_body=datafile('small-png'),
    889                                    media_mime_type='image/png')
    890     self.assertEquals('image/png', request.headers['content-type'])
    891     self.assertEquals(b'PNG', request.body[1:4])
    892     assertUrisEqual(self,
    893         'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
    894         request.uri)
    895 
    896   def test_multipart_media_raise_correct_exceptions(self):
    897     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    898     zoo = build('zoo', 'v1', http=self.http)
    899 
    900     try:
    901       zoo.animals().insert(media_body=datafile('smiley.png'), body={})
    902       self.fail("should throw exception if media is too large.")
    903     except MediaUploadSizeError:
    904       pass
    905 
    906     try:
    907       zoo.animals().insert(media_body=datafile('small.jpg'), body={})
    908       self.fail("should throw exception if mimetype is unacceptable.")
    909     except UnacceptableMimeTypeError:
    910       pass
    911 
    912   def test_multipart_media_good_upload(self):
    913     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    914     zoo = build('zoo', 'v1', http=self.http)
    915 
    916     request = zoo.animals().insert(media_body=datafile('small.png'), body={})
    917     self.assertTrue(request.headers['content-type'].startswith(
    918         'multipart/related'))
    919     with open(datafile('small.png'), 'rb') as f:
    920       contents = f.read()
    921     boundary = re.match(b'--=+([^=]+)', request.body).group(1)
    922     self.assertEqual(
    923       request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
    924       b'--===============' + boundary + b'==\n' +
    925       b'Content-Type: application/json\n' +
    926       b'MIME-Version: 1.0\n\n' +
    927       b'{"data": {}}\n' +
    928       b'--===============' + boundary + b'==\n' +
    929       b'Content-Type: image/png\n' +
    930       b'MIME-Version: 1.0\n' +
    931       b'Content-Transfer-Encoding: binary\n\n' +
    932       contents +
    933       b'\n--===============' + boundary + b'==--')
    934     assertUrisEqual(self,
    935         'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
    936         request.uri)
    937 
    938   def test_media_capable_method_without_media(self):
    939     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    940     zoo = build('zoo', 'v1', http=self.http)
    941 
    942     request = zoo.animals().insert(body={})
    943     self.assertTrue(request.headers['content-type'], 'application/json')
    944 
    945   def test_resumable_multipart_media_good_upload(self):
    946     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
    947     zoo = build('zoo', 'v1', http=self.http)
    948 
    949     media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
    950     request = zoo.animals().insert(media_body=media_upload, body={})
    951     self.assertTrue(request.headers['content-type'].startswith(
    952         'application/json'))
    953     self.assertEquals('{"data": {}}', request.body)
    954     self.assertEquals(media_upload, request.resumable)
    955 
    956     self.assertEquals('image/png', request.resumable.mimetype())
    957 
    958     self.assertNotEquals(request.body, None)
    959     self.assertEquals(request.resumable_uri, None)
    960 
    961     http = HttpMockSequence([
    962       ({'status': '200',
    963         'location': 'http://upload.example.com'}, ''),
    964       ({'status': '308',
    965         'location': 'http://upload.example.com/2'}, ''),
    966       ({'status': '308',
    967         'location': 'http://upload.example.com/3',
    968         'range': '0-12'}, ''),
    969       ({'status': '308',
    970         'location': 'http://upload.example.com/4',
    971         'range': '0-%d' % (media_upload.size() - 2)}, ''),
    972       ({'status': '200'}, '{"foo": "bar"}'),
    973       ])
    974 
    975     status, body = request.next_chunk(http=http)
    976     self.assertEquals(None, body)
    977     self.assertTrue(isinstance(status, MediaUploadProgress))
    978     self.assertEquals(0, status.resumable_progress)
    979 
    980     # Two requests should have been made and the resumable_uri should have been
    981     # updated for each one.
    982     self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
    983     self.assertEquals(media_upload, request.resumable)
    984     self.assertEquals(0, request.resumable_progress)
    985     
    986     # This next chuck call should upload the first chunk
    987     status, body = request.next_chunk(http=http)
    988     self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
    989     self.assertEquals(media_upload, request.resumable)
    990     self.assertEquals(13, request.resumable_progress)
    991 
    992     # This call will upload the next chunk
    993     status, body = request.next_chunk(http=http)
    994     self.assertEquals(request.resumable_uri, 'http://upload.example.com/4')
    995     self.assertEquals(media_upload.size()-1, request.resumable_progress)
    996     self.assertEquals('{"data": {}}', request.body)
    997 
    998     # Final call to next_chunk should complete the upload.
    999     status, body = request.next_chunk(http=http)
   1000     self.assertEquals(body, {"foo": "bar"})
   1001     self.assertEquals(status, None)
   1002 
   1003 
   1004   def test_resumable_media_good_upload(self):
   1005     """Not a multipart upload."""
   1006     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1007     zoo = build('zoo', 'v1', http=self.http)
   1008 
   1009     media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
   1010     request = zoo.animals().insert(media_body=media_upload, body=None)
   1011     self.assertEquals(media_upload, request.resumable)
   1012 
   1013     self.assertEquals('image/png', request.resumable.mimetype())
   1014 
   1015     self.assertEquals(request.body, None)
   1016     self.assertEquals(request.resumable_uri, None)
   1017 
   1018     http = HttpMockSequence([
   1019       ({'status': '200',
   1020         'location': 'http://upload.example.com'}, ''),
   1021       ({'status': '308',
   1022         'location': 'http://upload.example.com/2',
   1023         'range': '0-12'}, ''),
   1024       ({'status': '308',
   1025         'location': 'http://upload.example.com/3',
   1026         'range': '0-%d' % (media_upload.size() - 2)}, ''),
   1027       ({'status': '200'}, '{"foo": "bar"}'),
   1028       ])
   1029 
   1030     status, body = request.next_chunk(http=http)
   1031     self.assertEquals(None, body)
   1032     self.assertTrue(isinstance(status, MediaUploadProgress))
   1033     self.assertEquals(13, status.resumable_progress)
   1034 
   1035     # Two requests should have been made and the resumable_uri should have been
   1036     # updated for each one.
   1037     self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
   1038 
   1039     self.assertEquals(media_upload, request.resumable)
   1040     self.assertEquals(13, request.resumable_progress)
   1041 
   1042     status, body = request.next_chunk(http=http)
   1043     self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
   1044     self.assertEquals(media_upload.size()-1, request.resumable_progress)
   1045     self.assertEquals(request.body, None)
   1046 
   1047     # Final call to next_chunk should complete the upload.
   1048     status, body = request.next_chunk(http=http)
   1049     self.assertEquals(body, {"foo": "bar"})
   1050     self.assertEquals(status, None)
   1051 
   1052   def test_resumable_media_good_upload_from_execute(self):
   1053     """Not a multipart upload."""
   1054     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1055     zoo = build('zoo', 'v1', http=self.http)
   1056 
   1057     media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
   1058     request = zoo.animals().insert(media_body=media_upload, body=None)
   1059     assertUrisEqual(self,
   1060         'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
   1061         request.uri)
   1062 
   1063     http = HttpMockSequence([
   1064       ({'status': '200',
   1065         'location': 'http://upload.example.com'}, ''),
   1066       ({'status': '308',
   1067         'location': 'http://upload.example.com/2',
   1068         'range': '0-12'}, ''),
   1069       ({'status': '308',
   1070         'location': 'http://upload.example.com/3',
   1071         'range': '0-%d' % media_upload.size()}, ''),
   1072       ({'status': '200'}, '{"foo": "bar"}'),
   1073       ])
   1074 
   1075     body = request.execute(http=http)
   1076     self.assertEquals(body, {"foo": "bar"})
   1077 
   1078   def test_resumable_media_fail_unknown_response_code_first_request(self):
   1079     """Not a multipart upload."""
   1080     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1081     zoo = build('zoo', 'v1', http=self.http)
   1082 
   1083     media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
   1084     request = zoo.animals().insert(media_body=media_upload, body=None)
   1085 
   1086     http = HttpMockSequence([
   1087       ({'status': '400',
   1088         'location': 'http://upload.example.com'}, ''),
   1089       ])
   1090 
   1091     try:
   1092       request.execute(http=http)
   1093       self.fail('Should have raised ResumableUploadError.')
   1094     except ResumableUploadError as e:
   1095       self.assertEqual(400, e.resp.status)
   1096 
   1097   def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
   1098     """Not a multipart upload."""
   1099     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1100     zoo = build('zoo', 'v1', http=self.http)
   1101 
   1102     media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
   1103     request = zoo.animals().insert(media_body=media_upload, body=None)
   1104 
   1105     http = HttpMockSequence([
   1106       ({'status': '200',
   1107         'location': 'http://upload.example.com'}, ''),
   1108       ({'status': '400'}, ''),
   1109       ])
   1110 
   1111     self.assertRaises(HttpError, request.execute, http=http)
   1112     self.assertTrue(request._in_error_state)
   1113 
   1114     http = HttpMockSequence([
   1115       ({'status': '308',
   1116         'range': '0-5'}, ''),
   1117       ({'status': '308',
   1118         'range': '0-6'}, ''),
   1119       ])
   1120 
   1121     status, body = request.next_chunk(http=http)
   1122     self.assertEquals(status.resumable_progress, 7,
   1123       'Should have first checked length and then tried to PUT more.')
   1124     self.assertFalse(request._in_error_state)
   1125 
   1126     # Put it back in an error state.
   1127     http = HttpMockSequence([
   1128       ({'status': '400'}, ''),
   1129       ])
   1130     self.assertRaises(HttpError, request.execute, http=http)
   1131     self.assertTrue(request._in_error_state)
   1132 
   1133     # Pretend the last request that 400'd actually succeeded.
   1134     http = HttpMockSequence([
   1135       ({'status': '200'}, '{"foo": "bar"}'),
   1136       ])
   1137     status, body = request.next_chunk(http=http)
   1138     self.assertEqual(body, {'foo': 'bar'})
   1139 
   1140   def test_media_io_base_stream_unlimited_chunksize_resume(self):
   1141     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1142     zoo = build('zoo', 'v1', http=self.http)
   1143 
   1144     # Set up a seekable stream and try to upload in single chunk.
   1145     fd = BytesIO(b'01234"56789"')
   1146     media_upload = MediaIoBaseUpload(
   1147         fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
   1148 
   1149     request = zoo.animals().insert(media_body=media_upload, body=None)
   1150 
   1151     # The single chunk fails, restart at the right point.
   1152     http = HttpMockSequence([
   1153       ({'status': '200',
   1154         'location': 'http://upload.example.com'}, ''),
   1155       ({'status': '308',
   1156         'location': 'http://upload.example.com/2',
   1157         'range': '0-4'}, ''),
   1158       ({'status': '200'}, 'echo_request_body'),
   1159       ])
   1160 
   1161     body = request.execute(http=http)
   1162     self.assertEqual('56789', body)
   1163 
   1164   def test_media_io_base_stream_chunksize_resume(self):
   1165     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1166     zoo = build('zoo', 'v1', http=self.http)
   1167 
   1168     # Set up a seekable stream and try to upload in chunks.
   1169     fd = BytesIO(b'0123456789')
   1170     media_upload = MediaIoBaseUpload(
   1171         fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
   1172 
   1173     request = zoo.animals().insert(media_body=media_upload, body=None)
   1174 
   1175     # The single chunk fails, pull the content sent out of the exception.
   1176     http = HttpMockSequence([
   1177       ({'status': '200',
   1178         'location': 'http://upload.example.com'}, ''),
   1179       ({'status': '400'}, 'echo_request_body'),
   1180       ])
   1181 
   1182     try:
   1183       body = request.execute(http=http)
   1184     except HttpError as e:
   1185       self.assertEqual(b'01234', e.content)
   1186 
   1187   def test_resumable_media_handle_uploads_of_unknown_size(self):
   1188     http = HttpMockSequence([
   1189       ({'status': '200',
   1190         'location': 'http://upload.example.com'}, ''),
   1191       ({'status': '200'}, 'echo_request_headers_as_json'),
   1192       ])
   1193 
   1194     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1195     zoo = build('zoo', 'v1', http=self.http)
   1196 
   1197     # Create an upload that doesn't know the full size of the media.
   1198     class IoBaseUnknownLength(MediaUpload):
   1199       def chunksize(self):
   1200         return 10
   1201 
   1202       def mimetype(self):
   1203         return 'image/png'
   1204 
   1205       def size(self):
   1206         return None
   1207 
   1208       def resumable(self):
   1209         return True
   1210 
   1211       def getbytes(self, begin, length):
   1212         return '0123456789'
   1213 
   1214     upload = IoBaseUnknownLength()
   1215 
   1216     request = zoo.animals().insert(media_body=upload, body=None)
   1217     status, body = request.next_chunk(http=http)
   1218     self.assertEqual(body, {
   1219         'Content-Range': 'bytes 0-9/*',
   1220         'Content-Length': '10',
   1221         })
   1222 
   1223   def test_resumable_media_no_streaming_on_unsupported_platforms(self):
   1224     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1225     zoo = build('zoo', 'v1', http=self.http)
   1226 
   1227     class IoBaseHasStream(MediaUpload):
   1228       def chunksize(self):
   1229         return 10
   1230 
   1231       def mimetype(self):
   1232         return 'image/png'
   1233 
   1234       def size(self):
   1235         return None
   1236 
   1237       def resumable(self):
   1238         return True
   1239 
   1240       def getbytes(self, begin, length):
   1241         return '0123456789'
   1242 
   1243       def has_stream(self):
   1244         return True
   1245 
   1246       def stream(self):
   1247         raise NotImplementedError()
   1248 
   1249     upload = IoBaseHasStream()
   1250 
   1251     orig_version = sys.version_info
   1252 
   1253     sys.version_info = (2, 6, 5, 'final', 0)
   1254 
   1255     request = zoo.animals().insert(media_body=upload, body=None)
   1256 
   1257     # This should raise an exception because stream() will be called.
   1258     http = HttpMockSequence([
   1259       ({'status': '200',
   1260         'location': 'http://upload.example.com'}, ''),
   1261       ({'status': '200'}, 'echo_request_headers_as_json'),
   1262       ])
   1263 
   1264     self.assertRaises(NotImplementedError, request.next_chunk, http=http)
   1265 
   1266     sys.version_info = orig_version
   1267 
   1268   def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
   1269     http = HttpMockSequence([
   1270       ({'status': '200',
   1271         'location': 'http://upload.example.com'}, ''),
   1272       ({'status': '200'}, 'echo_request_headers_as_json'),
   1273       ])
   1274 
   1275     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1276     zoo = build('zoo', 'v1', http=self.http)
   1277 
   1278     fd = BytesIO(b'data goes here')
   1279 
   1280     # Create an upload that doesn't know the full size of the media.
   1281     upload = MediaIoBaseUpload(
   1282         fd=fd, mimetype='image/png', chunksize=15, resumable=True)
   1283 
   1284     request = zoo.animals().insert(media_body=upload, body=None)
   1285     status, body = request.next_chunk(http=http)
   1286     self.assertEqual(body, {
   1287         'Content-Range': 'bytes 0-13/14',
   1288         'Content-Length': '14',
   1289         })
   1290 
   1291   def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
   1292     http = HttpMockSequence([
   1293       ({'status': '200',
   1294         'location': 'http://upload.example.com'}, ''),
   1295       ({'status': '400'}, ''),
   1296       ])
   1297 
   1298     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1299     zoo = build('zoo', 'v1', http=self.http)
   1300 
   1301     # Create an upload that doesn't know the full size of the media.
   1302     fd = BytesIO(b'data goes here')
   1303 
   1304     upload = MediaIoBaseUpload(
   1305         fd=fd, mimetype='image/png', chunksize=500, resumable=True)
   1306 
   1307     request = zoo.animals().insert(media_body=upload, body=None)
   1308 
   1309     # Put it in an error state.
   1310     self.assertRaises(HttpError, request.next_chunk, http=http)
   1311 
   1312     http = HttpMockSequence([
   1313       ({'status': '400',
   1314         'range': '0-5'}, 'echo_request_headers_as_json'),
   1315       ])
   1316     try:
   1317       # Should resume the upload by first querying the status of the upload.
   1318       request.next_chunk(http=http)
   1319     except HttpError as e:
   1320       expected = {
   1321           'Content-Range': 'bytes */14',
   1322           'content-length': '0'
   1323           }
   1324       self.assertEqual(expected, json.loads(e.content.decode('utf-8')),
   1325         'Should send an empty body when requesting the current upload status.')
   1326 
   1327   def test_pickle(self):
   1328     sorted_resource_keys = ['_baseUrl',
   1329                             '_developerKey',
   1330                             '_dynamic_attrs',
   1331                             '_http',
   1332                             '_model',
   1333                             '_requestBuilder',
   1334                             '_resourceDesc',
   1335                             '_rootDesc',
   1336                             '_schema',
   1337                             'animals',
   1338                             'global_',
   1339                             'load',
   1340                             'loadNoTemplate',
   1341                             'my',
   1342                             'new_batch_http_request',
   1343                             'query',
   1344                             'scopedAnimals']
   1345 
   1346     http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1347     zoo = build('zoo', 'v1', http=http)
   1348     self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
   1349 
   1350     pickled_zoo = pickle.dumps(zoo)
   1351     new_zoo = pickle.loads(pickled_zoo)
   1352     self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
   1353     self.assertTrue(hasattr(new_zoo, 'animals'))
   1354     self.assertTrue(callable(new_zoo.animals))
   1355     self.assertTrue(hasattr(new_zoo, 'global_'))
   1356     self.assertTrue(callable(new_zoo.global_))
   1357     self.assertTrue(hasattr(new_zoo, 'load'))
   1358     self.assertTrue(callable(new_zoo.load))
   1359     self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
   1360     self.assertTrue(callable(new_zoo.loadNoTemplate))
   1361     self.assertTrue(hasattr(new_zoo, 'my'))
   1362     self.assertTrue(callable(new_zoo.my))
   1363     self.assertTrue(hasattr(new_zoo, 'query'))
   1364     self.assertTrue(callable(new_zoo.query))
   1365     self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
   1366     self.assertTrue(callable(new_zoo.scopedAnimals))
   1367 
   1368     self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
   1369     self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
   1370     self.assertEqual(zoo._developerKey, new_zoo._developerKey)
   1371     self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
   1372     self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
   1373     self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
   1374     # _http, _model and _schema won't be equal since we will get new
   1375     # instances upon un-pickling
   1376 
   1377   def _dummy_zoo_request(self):
   1378     with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
   1379       zoo_contents = fh.read()
   1380 
   1381     zoo_uri = uritemplate.expand(DISCOVERY_URI,
   1382                                  {'api': 'zoo', 'apiVersion': 'v1'})
   1383     if 'REMOTE_ADDR' in os.environ:
   1384         zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
   1385                                             os.environ['REMOTE_ADDR'])
   1386 
   1387     http = build_http()
   1388     original_request = http.request
   1389     def wrapped_request(uri, method='GET', *args, **kwargs):
   1390         if uri == zoo_uri:
   1391           return httplib2.Response({'status': '200'}), zoo_contents
   1392         return original_request(uri, method=method, *args, **kwargs)
   1393     http.request = wrapped_request
   1394     return http
   1395 
   1396   def _dummy_token(self):
   1397     access_token = 'foo'
   1398     client_id = 'some_client_id'
   1399     client_secret = 'cOuDdkfjxxnv+'
   1400     refresh_token = '1/0/a.df219fjls0'
   1401     token_expiry = datetime.datetime.utcnow()
   1402     user_agent = 'refresh_checker/1.0'
   1403     return OAuth2Credentials(
   1404         access_token, client_id, client_secret,
   1405         refresh_token, token_expiry, GOOGLE_TOKEN_URI,
   1406         user_agent)
   1407 
   1408   def test_pickle_with_credentials(self):
   1409     credentials = self._dummy_token()
   1410     http = self._dummy_zoo_request()
   1411     http = credentials.authorize(http)
   1412     self.assertTrue(hasattr(http.request, 'credentials'))
   1413 
   1414     zoo = build('zoo', 'v1', http=http)
   1415     pickled_zoo = pickle.dumps(zoo)
   1416     new_zoo = pickle.loads(pickled_zoo)
   1417     self.assertEqual(sorted(zoo.__dict__.keys()),
   1418                      sorted(new_zoo.__dict__.keys()))
   1419     new_http = new_zoo._http
   1420     self.assertFalse(hasattr(new_http.request, 'credentials'))
   1421 
   1422   def test_resumable_media_upload_no_content(self):
   1423     self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1424     zoo = build('zoo', 'v1', http=self.http)
   1425 
   1426     media_upload = MediaFileUpload(datafile('empty'), resumable=True)
   1427     request = zoo.animals().insert(media_body=media_upload, body=None)
   1428 
   1429     self.assertEquals(media_upload, request.resumable)
   1430     self.assertEquals(request.body, None)
   1431     self.assertEquals(request.resumable_uri, None)
   1432 
   1433     http = HttpMockSequence([
   1434       ({'status': '200',
   1435         'location': 'http://upload.example.com'}, ''),
   1436       ({'status': '308',
   1437         'location': 'http://upload.example.com/2',
   1438         'range': '0-0'}, ''),
   1439     ])
   1440 
   1441     status, body = request.next_chunk(http=http)
   1442     self.assertEquals(None, body)
   1443     self.assertTrue(isinstance(status, MediaUploadProgress))
   1444     self.assertEquals(0, status.progress())
   1445 
   1446 
   1447 class Next(unittest.TestCase):
   1448 
   1449   def test_next_successful_none_on_no_next_page_token(self):
   1450     self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
   1451     tasks = build('tasks', 'v1', http=self.http)
   1452     request = tasks.tasklists().list()
   1453     self.assertEqual(None, tasks.tasklists().list_next(request, {}))
   1454 
   1455   def test_next_successful_none_on_empty_page_token(self):
   1456     self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
   1457     tasks = build('tasks', 'v1', http=self.http)
   1458     request = tasks.tasklists().list()
   1459     next_request = tasks.tasklists().list_next(
   1460         request, {'nextPageToken': ''})
   1461     self.assertEqual(None, next_request)
   1462 
   1463   def test_next_successful_with_next_page_token(self):
   1464     self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
   1465     tasks = build('tasks', 'v1', http=self.http)
   1466     request = tasks.tasklists().list()
   1467     next_request = tasks.tasklists().list_next(
   1468         request, {'nextPageToken': '123abc'})
   1469     parsed = list(urlparse(next_request.uri))
   1470     q = parse_qs(parsed[4])
   1471     self.assertEqual(q['pageToken'][0], '123abc')
   1472 
   1473   def test_next_successful_with_next_page_token_alternate_name(self):
   1474     self.http = HttpMock(datafile('bigquery.json'), {'status': '200'})
   1475     bigquery = build('bigquery', 'v2', http=self.http)
   1476     request = bigquery.tabledata().list(datasetId='', projectId='', tableId='')
   1477     next_request = bigquery.tabledata().list_next(
   1478         request, {'pageToken': '123abc'})
   1479     parsed = list(urlparse(next_request.uri))
   1480     q = parse_qs(parsed[4])
   1481     self.assertEqual(q['pageToken'][0], '123abc')
   1482 
   1483   def test_next_successful_with_next_page_token_in_body(self):
   1484     self.http = HttpMock(datafile('logging.json'), {'status': '200'})
   1485     logging = build('logging', 'v2', http=self.http)
   1486     request = logging.entries().list(body={})
   1487     next_request = logging.entries().list_next(
   1488         request, {'nextPageToken': '123abc'})
   1489     body = JsonModel().deserialize(next_request.body)
   1490     self.assertEqual(body['pageToken'], '123abc')
   1491 
   1492   def test_next_with_method_with_no_properties(self):
   1493     self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
   1494     service = build('latitude', 'v1', http=self.http)
   1495     service.currentLocation().get()
   1496 
   1497   def test_next_nonexistent_with_no_next_page_token(self):
   1498     self.http = HttpMock(datafile('drive.json'), {'status': '200'})
   1499     drive = build('drive', 'v3', http=self.http)
   1500     drive.changes().watch(body={})
   1501     self.assertFalse(callable(getattr(drive.changes(), 'watch_next', None)))
   1502 
   1503   def test_next_successful_with_next_page_token_required(self):
   1504     self.http = HttpMock(datafile('drive.json'), {'status': '200'})
   1505     drive = build('drive', 'v3', http=self.http)
   1506     request = drive.changes().list(pageToken='startPageToken')
   1507     next_request = drive.changes().list_next(
   1508         request, {'nextPageToken': '123abc'})
   1509     parsed = list(urlparse(next_request.uri))
   1510     q = parse_qs(parsed[4])
   1511     self.assertEqual(q['pageToken'][0], '123abc')
   1512 
   1513 
   1514 class MediaGet(unittest.TestCase):
   1515 
   1516   def test_get_media(self):
   1517     http = HttpMock(datafile('zoo.json'), {'status': '200'})
   1518     zoo = build('zoo', 'v1', http=http)
   1519     request = zoo.animals().get_media(name='Lion')
   1520 
   1521     parsed = urlparse(request.uri)
   1522     q = parse_qs(parsed[4])
   1523     self.assertEqual(q['alt'], ['media'])
   1524     self.assertEqual(request.headers['accept'], '*/*')
   1525 
   1526     http = HttpMockSequence([
   1527       ({'status': '200'}, 'standing in for media'),
   1528       ])
   1529     response = request.execute(http=http)
   1530     self.assertEqual(b'standing in for media', response)
   1531 
   1532 
   1533 if __name__ == '__main__':
   1534   unittest.main()
   1535