blob: b41051aecb77dc9a727c8b3b9aa3cb6795f26cec [file] [log] [blame]
Craig Citro15744b12015-03-02 13:34:32 -08001#!/usr/bin/env python
Joe Gregoriof863f7a2011-02-24 03:24:44 -05002# -*- coding: utf-8 -*-
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04003#
Craig Citro751b7fb2014-09-23 11:20:38 -07004# Copyright 2014 Google Inc. All Rights Reserved.
Joe Gregorio6d5e94f2010-08-25 23:49:30 -04005#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040018
19"""Discovery document tests
20
21Unit tests for objects created from discovery documents.
22"""
INADA Naokid898a372015-03-04 03:52:46 +090023from __future__ import absolute_import
24import six
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040025
26__author__ = 'jcgregorio@google.com (Joe Gregorio)'
27
Pat Ferateed9affd2015-03-03 16:03:15 -080028from six import BytesIO, StringIO
Pat Ferated5b61bd2015-03-03 16:04:11 -080029from six.moves.urllib.parse import urlparse, parse_qs
Pat Ferateed9affd2015-03-03 16:03:15 -080030
Daniel Hermesc2113242013-02-27 10:16:13 -080031import copy
Joe Gregoriodc106fc2012-11-20 14:30:14 -050032import datetime
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040033import httplib2
Craig Citro7ee535d2015-02-23 10:11:14 -080034import itertools
Craig Citro6ae34d72014-08-18 23:10:09 -070035import json
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040036import os
Joe Gregoriodc106fc2012-11-20 14:30:14 -050037import pickle
Phil Ruffwind26178fc2015-10-13 19:00:33 -040038import re
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040039import sys
Pat Ferate497a90f2015-03-09 09:52:54 -070040import unittest2 as unittest
Joe Gregoriodc106fc2012-11-20 14:30:14 -050041
Takashi Matsuo30125122015-08-19 11:42:32 -070042import mock
43
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080044import google.auth.credentials
45import google_auth_httplib2
John Asmuth864311d2014-04-24 15:46:08 -040046from googleapiclient.discovery import _fix_up_media_upload
47from googleapiclient.discovery import _fix_up_method_description
48from googleapiclient.discovery import _fix_up_parameters
Craig Citro7ee535d2015-02-23 10:11:14 -080049from googleapiclient.discovery import _urljoin
John Asmuth864311d2014-04-24 15:46:08 -040050from googleapiclient.discovery import build
51from googleapiclient.discovery import build_from_document
52from googleapiclient.discovery import DISCOVERY_URI
53from googleapiclient.discovery import key2param
54from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
Brian J. Watson38051ac2016-10-25 07:53:08 -070055from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE
John Asmuth864311d2014-04-24 15:46:08 -040056from googleapiclient.discovery import ResourceMethodParameters
57from googleapiclient.discovery import STACK_QUERY_PARAMETERS
58from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Takashi Matsuo30125122015-08-19 11:42:32 -070059from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE
60from googleapiclient.discovery_cache.base import Cache
John Asmuth864311d2014-04-24 15:46:08 -040061from googleapiclient.errors import HttpError
62from googleapiclient.errors import InvalidJsonError
63from googleapiclient.errors import MediaUploadSizeError
64from googleapiclient.errors import ResumableUploadError
65from googleapiclient.errors import UnacceptableMimeTypeError
Takashi Matsuo3772f9d2015-09-04 12:25:55 -070066from googleapiclient.errors import UnknownApiNameOrVersion
Brian J. Watson38051ac2016-10-25 07:53:08 -070067from googleapiclient.errors import UnknownFileType
Igor Maravić22435292017-01-19 22:28:22 +010068from googleapiclient.http import build_http
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -040069from googleapiclient.http import BatchHttpRequest
John Asmuth864311d2014-04-24 15:46:08 -040070from googleapiclient.http import HttpMock
71from googleapiclient.http import HttpMockSequence
72from googleapiclient.http import MediaFileUpload
73from googleapiclient.http import MediaIoBaseUpload
74from googleapiclient.http import MediaUpload
75from googleapiclient.http import MediaUploadProgress
76from googleapiclient.http import tunnel_patch
Thomas Coffee20af04d2017-02-10 15:24:44 -080077from googleapiclient.model import JsonModel
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +090078from googleapiclient.schema import Schemas
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080079from oauth2client import GOOGLE_TOKEN_URI
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -080080from oauth2client.client import OAuth2Credentials, GoogleCredentials
Joe Gregorio79daca02013-03-29 16:25:52 -040081
Helen Koikede13e3b2018-04-26 16:05:16 -030082from googleapiclient import _helpers as util
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -070083
Joe Gregoriodc106fc2012-11-20 14:30:14 -050084import uritemplate
85
Joe Gregoriocb8103d2011-02-11 23:20:52 -050086
87DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
88
Joe Gregorioa98733f2011-09-16 10:12:28 -040089
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050090def assertUrisEqual(testcase, expected, actual):
91 """Test that URIs are the same, up to reordering of query parameters."""
Pat Ferated5b61bd2015-03-03 16:04:11 -080092 expected = urlparse(expected)
93 actual = urlparse(actual)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050094 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)
INADA Naokid898a372015-03-04 03:52:46 +0900101 for name in list(expected_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500102 testcase.assertEqual(expected_query[name], actual_query[name])
INADA Naokid898a372015-03-04 03:52:46 +0900103 for name in list(actual_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500104 testcase.assertEqual(expected_query[name], actual_query[name])
105
106
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500107def datafile(filename):
108 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400109
110
Joe Gregorio504a17f2012-12-07 14:14:26 -0500111class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800112
Joe Gregorio504a17f2012-12-07 14:14:26 -0500113 def test_retries(self):
John Asmuth864311d2014-04-24 15:46:08 -0400114 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
Joe Gregorio504a17f2012-12-07 14:14:26 -0500115 self.assertEqual(1, httplib2.RETRIES)
116
117
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400118class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800119
120 def setUp(self):
121 with open(datafile('zoo.json'), 'r') as fh:
Craig Citro6ae34d72014-08-18 23:10:09 -0700122 self.zoo_root_desc = json.loads(fh.read())
Daniel Hermesc2113242013-02-27 10:16:13 -0800123 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800124 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
125 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900126 self.zoo_schema = Schemas(self.zoo_root_desc)
Daniel Hermesc2113242013-02-27 10:16:13 -0800127
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400128 def test_key2param(self):
129 self.assertEqual('max_results', key2param('max-results'))
130 self.assertEqual('x007_bond', key2param('007-bond'))
131
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900132 def _base_fix_up_parameters_test(
133 self, method_desc, http_method, root_desc, schema):
Daniel Hermesc2113242013-02-27 10:16:13 -0800134 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
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900139 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method,
140 schema)
Daniel Hermesc2113242013-02-27 10:16:13 -0800141
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
INADA Naokid898a372015-03-04 03:52:46 +0900148 for param_name, value in six.iteritems(root_desc.get('parameters', {})):
Daniel Hermesc2113242013-02-27 10:16:13 -0800149 self.assertEqual(value, parameters[param_name])
150
151 return parameters
152
153 def test_fix_up_parameters_get(self):
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900154 parameters = self._base_fix_up_parameters_test(
155 self.zoo_get_method_desc, 'GET', self.zoo_root_desc, self.zoo_schema)
Daniel Hermesc2113242013-02-27 10:16:13 -0800156 # Since http_method is 'GET'
INADA Naoki0bceb332014-08-20 15:27:52 +0900157 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800158
159 def test_fix_up_parameters_insert(self):
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900160 parameters = self._base_fix_up_parameters_test(
161 self.zoo_insert_method_desc, 'POST', self.zoo_root_desc, self.zoo_schema)
Daniel Hermesc2113242013-02-27 10:16:13 -0800162 body = {
163 'description': 'The request body.',
164 'type': 'object',
Daniel Hermesc2113242013-02-27 10:16:13 -0800165 '$ref': 'Animal',
166 }
167 self.assertEqual(parameters['body'], body)
168
169 def test_fix_up_parameters_check_body(self):
170 dummy_root_desc = {}
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900171 dummy_schema = {
172 'Request': {
173 'properties': {
174 "description": "Required. Dummy parameter.",
175 "type": "string"
176 }
177 }
178 }
Daniel Hermesc2113242013-02-27 10:16:13 -0800179 no_payload_http_method = 'DELETE'
180 with_payload_http_method = 'PUT'
181
182 invalid_method_desc = {'response': 'Who cares'}
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900183 valid_method_desc = {
184 'request': {
185 'key1': 'value1',
186 'key2': 'value2',
187 '$ref': 'Request'
188 }
189 }
Daniel Hermesc2113242013-02-27 10:16:13 -0800190
191 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900192 no_payload_http_method, dummy_schema)
INADA Naoki0bceb332014-08-20 15:27:52 +0900193 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800194
195 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900196 no_payload_http_method, dummy_schema)
INADA Naoki0bceb332014-08-20 15:27:52 +0900197 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800198
199 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900200 with_payload_http_method, dummy_schema)
INADA Naoki0bceb332014-08-20 15:27:52 +0900201 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800202
203 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900204 with_payload_http_method, dummy_schema)
Daniel Hermesc2113242013-02-27 10:16:13 -0800205 body = {
206 'description': 'The request body.',
207 'type': 'object',
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900208 '$ref': 'Request',
Daniel Hermesc2113242013-02-27 10:16:13 -0800209 'key1': 'value1',
210 'key2': 'value2',
211 }
212 self.assertEqual(parameters['body'], body)
213
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900214 def test_fix_up_parameters_optional_body(self):
215 # Request with no parameters
216 dummy_schema = {'Request': {'properties': {}}}
217 method_desc = {'request': {'$ref': 'Request'}}
218
219 parameters = _fix_up_parameters(method_desc, {}, 'POST', dummy_schema)
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900220
Daniel Hermesc2113242013-02-27 10:16:13 -0800221 def _base_fix_up_method_description_test(
222 self, method_desc, initial_parameters, final_parameters,
223 final_accept, final_max_size, final_media_path_url):
224 fake_root_desc = {'rootUrl': 'http://root/',
225 'servicePath': 'fake/'}
226 fake_path_url = 'fake-path/'
227
228 accept, max_size, media_path_url = _fix_up_media_upload(
229 method_desc, fake_root_desc, fake_path_url, initial_parameters)
230 self.assertEqual(accept, final_accept)
231 self.assertEqual(max_size, final_max_size)
232 self.assertEqual(media_path_url, final_media_path_url)
233 self.assertEqual(initial_parameters, final_parameters)
234
235 def test_fix_up_media_upload_no_initial_invalid(self):
236 invalid_method_desc = {'response': 'Who cares'}
237 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
238 [], 0, None)
239
240 def test_fix_up_media_upload_no_initial_valid_minimal(self):
241 valid_method_desc = {'mediaUpload': {'accept': []}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700242 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
243 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800244 self._base_fix_up_method_description_test(
245 valid_method_desc, {}, final_parameters, [], 0,
246 'http://root/upload/fake/fake-path/')
247
248 def test_fix_up_media_upload_no_initial_valid_full(self):
249 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700250 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
251 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800252 ten_gb = 10 * 2**30
253 self._base_fix_up_method_description_test(
254 valid_method_desc, {}, final_parameters, ['*/*'],
255 ten_gb, 'http://root/upload/fake/fake-path/')
256
257 def test_fix_up_media_upload_with_initial_invalid(self):
258 invalid_method_desc = {'response': 'Who cares'}
259 initial_parameters = {'body': {}}
260 self._base_fix_up_method_description_test(
261 invalid_method_desc, initial_parameters,
262 initial_parameters, [], 0, None)
263
264 def test_fix_up_media_upload_with_initial_valid_minimal(self):
265 valid_method_desc = {'mediaUpload': {'accept': []}}
266 initial_parameters = {'body': {}}
Bu Sun Kimb854ff12019-07-16 17:46:08 -0700267 final_parameters = {'body': {},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700268 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
269 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800270 self._base_fix_up_method_description_test(
271 valid_method_desc, initial_parameters, final_parameters, [], 0,
272 'http://root/upload/fake/fake-path/')
273
274 def test_fix_up_media_upload_with_initial_valid_full(self):
275 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
276 initial_parameters = {'body': {}}
Bu Sun Kimb854ff12019-07-16 17:46:08 -0700277 final_parameters = {'body': {},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700278 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
279 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800280 ten_gb = 10 * 2**30
281 self._base_fix_up_method_description_test(
282 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
283 ten_gb, 'http://root/upload/fake/fake-path/')
284
285 def test_fix_up_method_description_get(self):
286 result = _fix_up_method_description(self.zoo_get_method_desc,
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900287 self.zoo_root_desc, self.zoo_schema)
Daniel Hermesc2113242013-02-27 10:16:13 -0800288 path_url = 'query'
289 http_method = 'GET'
290 method_id = 'bigquery.query'
291 accept = []
INADA Naoki0bceb332014-08-20 15:27:52 +0900292 max_size = 0
Daniel Hermesc2113242013-02-27 10:16:13 -0800293 media_path_url = None
294 self.assertEqual(result, (path_url, http_method, method_id, accept,
295 max_size, media_path_url))
296
297 def test_fix_up_method_description_insert(self):
298 result = _fix_up_method_description(self.zoo_insert_method_desc,
Jean-Loup Roussel-Clouet0c0c8972018-04-28 05:42:43 +0900299 self.zoo_root_desc, self.zoo_schema)
Daniel Hermesc2113242013-02-27 10:16:13 -0800300 path_url = 'animals'
301 http_method = 'POST'
302 method_id = 'zoo.animals.insert'
303 accept = ['image/png']
INADA Naoki0bceb332014-08-20 15:27:52 +0900304 max_size = 1024
Daniel Hermesc2113242013-02-27 10:16:13 -0800305 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
306 self.assertEqual(result, (path_url, http_method, method_id, accept,
307 max_size, media_path_url))
308
Craig Citro7ee535d2015-02-23 10:11:14 -0800309 def test_urljoin(self):
310 # We want to exhaustively test various URL combinations.
311 simple_bases = ['https://www.googleapis.com', 'https://www.googleapis.com/']
312 long_urls = ['foo/v1/bar:custom?alt=json', '/foo/v1/bar:custom?alt=json']
313
314 long_bases = [
315 'https://www.googleapis.com/foo/v1',
316 'https://www.googleapis.com/foo/v1/',
317 ]
318 simple_urls = ['bar:custom?alt=json', '/bar:custom?alt=json']
319
320 final_url = 'https://www.googleapis.com/foo/v1/bar:custom?alt=json'
321 for base, url in itertools.product(simple_bases, long_urls):
322 self.assertEqual(final_url, _urljoin(base, url))
323 for base, url in itertools.product(long_bases, simple_urls):
324 self.assertEqual(final_url, _urljoin(base, url))
325
326
Daniel Hermes954e1242013-02-28 09:28:37 -0800327 def test_ResourceMethodParameters_zoo_get(self):
328 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
329
330 param_types = {'a': 'any',
331 'b': 'boolean',
332 'e': 'string',
333 'er': 'string',
334 'i': 'integer',
335 'n': 'number',
336 'o': 'object',
337 'q': 'string',
338 'rr': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900339 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800340 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
341 self.assertEqual(parameters.required_params, [])
342 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
343 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
344 self.assertEqual(sorted(parameters.query_params),
345 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
346 self.assertEqual(parameters.path_params, set())
347 self.assertEqual(parameters.param_types, param_types)
348 enum_params = {'e': ['foo', 'bar'],
349 'er': ['one', 'two', 'three']}
350 self.assertEqual(parameters.enum_params, enum_params)
351
352 def test_ResourceMethodParameters_zoo_animals_patch(self):
353 method_desc = self.zoo_animals_resource['methods']['patch']
354 parameters = ResourceMethodParameters(method_desc)
355
356 param_types = {'name': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900357 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800358 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
359 self.assertEqual(parameters.required_params, ['name'])
360 self.assertEqual(parameters.repeated_params, [])
361 self.assertEqual(parameters.pattern_params, {})
362 self.assertEqual(parameters.query_params, [])
363 self.assertEqual(parameters.path_params, set(['name']))
364 self.assertEqual(parameters.param_types, param_types)
365 self.assertEqual(parameters.enum_params, {})
366
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400367
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500368class DiscoveryErrors(unittest.TestCase):
369
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400370 def test_tests_should_be_run_with_strict_positional_enforcement(self):
371 try:
372 plus = build('plus', 'v1', None)
373 self.fail("should have raised a TypeError exception over missing http=.")
374 except TypeError:
375 pass
376
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500377 def test_failed_to_parse_discovery_json(self):
378 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
379 try:
Takashi Matsuo30125122015-08-19 11:42:32 -0700380 plus = build('plus', 'v1', http=self.http, cache_discovery=False)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500381 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500382 except InvalidJsonError:
383 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500384
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700385 def test_unknown_api_name_or_version(self):
386 http = HttpMockSequence([
387 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Ethan Bao12b7cd32016-03-14 14:25:10 -0700388 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700389 ])
390 with self.assertRaises(UnknownApiNameOrVersion):
391 plus = build('plus', 'v1', http=http, cache_discovery=False)
392
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800393 def test_credentials_and_http_mutually_exclusive(self):
394 http = HttpMock(datafile('plus.json'), {'status': '200'})
395 with self.assertRaises(ValueError):
396 build(
397 'plus', 'v1', http=http, credentials=mock.sentinel.credentials)
398
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500399
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100400class DiscoveryFromDocument(unittest.TestCase):
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800401 MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400402
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100403 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400404 discovery = open(datafile('plus.json')).read()
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800405 plus = build_from_document(
406 discovery, base="https://www.googleapis.com/",
407 credentials=self.MOCK_CREDENTIALS)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500408 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500409 self.assertTrue(hasattr(plus, 'activities'))
410
411 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400412 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700413 discovery = json.loads(discovery)
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800414 plus = build_from_document(
415 discovery, base="https://www.googleapis.com/",
416 credentials=self.MOCK_CREDENTIALS)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500417 self.assertTrue(plus is not None)
418 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400419
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100420 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400421 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400422
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100423 base = "https://www.example.com/"
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800424 plus = build_from_document(
425 discovery, base=base, credentials=self.MOCK_CREDENTIALS)
Joe Gregorioa2838152012-07-16 11:52:17 -0400426 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100427
Igor Maravić22435292017-01-19 22:28:22 +0100428 def test_building_with_optional_http_with_authorization(self):
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700429 discovery = open(datafile('plus.json')).read()
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800430 plus = build_from_document(
431 discovery, base="https://www.googleapis.com/",
432 credentials=self.MOCK_CREDENTIALS)
Igor Maravić22435292017-01-19 22:28:22 +0100433
434 # plus service requires Authorization, hence we expect to see AuthorizedHttp object here
435 self.assertIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
436 self.assertIsInstance(plus._http.http, httplib2.Http)
437 self.assertIsInstance(plus._http.http.timeout, int)
438 self.assertGreater(plus._http.http.timeout, 0)
439
440 def test_building_with_optional_http_with_no_authorization(self):
441 discovery = open(datafile('plus.json')).read()
442 # Cleanup auth field, so we would use plain http client
443 discovery = json.loads(discovery)
444 discovery['auth'] = {}
445 discovery = json.dumps(discovery)
446
447 plus = build_from_document(
448 discovery, base="https://www.googleapis.com/",
Arunpn9d779cc2018-11-30 10:25:01 -0800449 credentials=None)
Igor Maravić22435292017-01-19 22:28:22 +0100450 # plus service requires Authorization
451 self.assertIsInstance(plus._http, httplib2.Http)
452 self.assertIsInstance(plus._http.timeout, int)
453 self.assertGreater(plus._http.timeout, 0)
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700454
455 def test_building_with_explicit_http(self):
456 http = HttpMock()
457 discovery = open(datafile('plus.json')).read()
458 plus = build_from_document(
459 discovery, base="https://www.googleapis.com/", http=http)
460 self.assertEquals(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100461
Jon Wayne Parrott068eb352017-02-08 10:13:06 -0800462 def test_building_with_developer_key_skips_adc(self):
463 discovery = open(datafile('plus.json')).read()
464 plus = build_from_document(
465 discovery, base="https://www.googleapis.com/", developerKey='123')
466 self.assertIsInstance(plus._http, httplib2.Http)
467 # It should not be an AuthorizedHttp, because that would indicate that
468 # application default credentials were used.
469 self.assertNotIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
470
471
Joe Gregorioa98733f2011-09-16 10:12:28 -0400472class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400473 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700474 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400475
Joe Gregorio583d9e42011-09-16 15:54:15 -0400476 def tearDown(self):
477 os.environ = self.old_environ
478
479 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400480 # build() will raise an HttpError on a 400, use this to pick the request uri
481 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400482 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400483 try:
484 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400485 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400486 ])
Arunpn9d779cc2018-11-30 10:25:01 -0800487 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400488 discoveryServiceUrl='http://example.com')
489 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900490 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400491 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400492
Joe Gregorio583d9e42011-09-16 15:54:15 -0400493 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400494 # build() will raise an HttpError on a 400, use this to pick the request uri
495 # out of the raised exception.
496 try:
497 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400498 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400499 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400500 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400501 discoveryServiceUrl='http://example.com')
502 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900503 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400504 self.assertEqual(e.uri, 'http://example.com')
505
Arunpn9d779cc2018-11-30 10:25:01 -0800506 def test_key_is_added_to_discovery_uri(self):
507 # build() will raise an HttpError on a 400, use this to pick the request uri
508 # out of the raised exception.
509 try:
510 http = HttpMockSequence([
511 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
512 ])
513 zoo = build('zoo', 'v1', http=http, developerKey='foo',
514 discoveryServiceUrl='http://example.com')
515 self.fail('Should have raised an exception.')
516 except HttpError as e:
517 self.assertEqual(e.uri, 'http://example.com?key=foo')
518
Ethan Bao12b7cd32016-03-14 14:25:10 -0700519 def test_discovery_loading_from_v2_discovery_uri(self):
520 http = HttpMockSequence([
521 ({'status': '404'}, 'Not found'),
522 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
523 ])
524 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
525 self.assertTrue(hasattr(zoo, 'animals'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400526
Takashi Matsuo30125122015-08-19 11:42:32 -0700527class DiscoveryFromAppEngineCache(unittest.TestCase):
528 def test_appengine_memcache(self):
529 # Hack module import
530 self.orig_import = __import__
531 self.mocked_api = mock.MagicMock()
532
eesheeshc6425a02016-02-12 15:07:06 +0000533 def import_mock(name, *args, **kwargs):
Takashi Matsuo30125122015-08-19 11:42:32 -0700534 if name == 'google.appengine.api':
535 return self.mocked_api
eesheeshc6425a02016-02-12 15:07:06 +0000536 return self.orig_import(name, *args, **kwargs)
Takashi Matsuo30125122015-08-19 11:42:32 -0700537
538 import_fullname = '__builtin__.__import__'
539 if sys.version_info[0] >= 3:
540 import_fullname = 'builtins.__import__'
541
542 with mock.patch(import_fullname, side_effect=import_mock):
543 namespace = 'google-api-client'
544 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
545
546 self.mocked_api.memcache.get.return_value = None
547
548 plus = build('plus', 'v1', http=self.http)
549
550 # memcache.get is called once
551 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
552 self.mocked_api.memcache.get.assert_called_once_with(url,
553 namespace=namespace)
554
555 # memcache.set is called once
556 with open(datafile('plus.json')) as f:
557 content = f.read()
558 self.mocked_api.memcache.set.assert_called_once_with(
559 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace)
560
561 # Returns the cached content this time.
562 self.mocked_api.memcache.get.return_value = content
563
564 # Make sure the contents are returned from the cache.
565 # (Otherwise it should through an error)
566 self.http = HttpMock(None, {'status': '200'})
567
568 plus = build('plus', 'v1', http=self.http)
569
570 # memcache.get is called twice
571 self.mocked_api.memcache.get.assert_has_calls(
572 [mock.call(url, namespace=namespace),
573 mock.call(url, namespace=namespace)])
574
575 # memcahce.set is called just once
576 self.mocked_api.memcache.set.assert_called_once_with(
577 url, content, time=DISCOVERY_DOC_MAX_AGE,namespace=namespace)
578
579
580class DictCache(Cache):
581 def __init__(self):
582 self.d = {}
583 def get(self, url):
584 return self.d.get(url, None)
585 def set(self, url, content):
586 self.d[url] = content
587 def contains(self, url):
588 return url in self.d
589
590
591class DiscoveryFromFileCache(unittest.TestCase):
592 def test_file_based_cache(self):
593 cache = mock.Mock(wraps=DictCache())
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -0700594 with mock.patch('googleapiclient.discovery_cache.autodetect',
595 return_value=cache):
Takashi Matsuo30125122015-08-19 11:42:32 -0700596 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
597
598 plus = build('plus', 'v1', http=self.http)
599
600 # cache.get is called once
601 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
602 cache.get.assert_called_once_with(url)
603
604 # cache.set is called once
605 with open(datafile('plus.json')) as f:
606 content = f.read()
607 cache.set.assert_called_once_with(url, content)
608
609 # Make sure there is a cache entry for the plus v1 discovery doc.
610 self.assertTrue(cache.contains(url))
611
612 # Make sure the contents are returned from the cache.
613 # (Otherwise it should through an error)
614 self.http = HttpMock(None, {'status': '200'})
615
616 plus = build('plus', 'v1', http=self.http)
617
618 # cache.get is called twice
619 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
620
621 # cahce.set is called just once
622 cache.set.assert_called_once_with(url, content)
623
624
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400625class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400626
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400627 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500628 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400629 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400630
631 # Missing required parameters
632 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500633 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400634 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900635 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400636 self.assertTrue('Missing' in str(e))
637
Joe Gregorio2467afa2012-06-20 12:21:25 -0400638 # Missing required parameters even if supplied as None.
639 try:
640 plus.activities().list(collection=None, userId=None)
641 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900642 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400643 self.assertTrue('Missing' in str(e))
644
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400645 # Parameter doesn't match regex
646 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500647 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400648 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900649 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500650 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400651
652 # Unexpected parameter
653 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500654 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400655 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900656 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400657 self.assertTrue('unexpected' in str(e))
658
Joe Gregoriobee86832011-02-22 10:00:19 -0500659 def _check_query_types(self, request):
Pat Ferated5b61bd2015-03-03 16:04:11 -0800660 parsed = urlparse(request.uri)
Joe Gregoriobee86832011-02-22 10:00:19 -0500661 q = parse_qs(parsed[4])
662 self.assertEqual(q['q'], ['foo'])
663 self.assertEqual(q['i'], ['1'])
664 self.assertEqual(q['n'], ['1.0'])
665 self.assertEqual(q['b'], ['false'])
666 self.assertEqual(q['a'], ['[1, 2, 3]'])
667 self.assertEqual(q['o'], ['{\'a\': 1}'])
668 self.assertEqual(q['e'], ['bar'])
669
670 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400671 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400672 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500673
Joe Gregorioa98733f2011-09-16 10:12:28 -0400674 request = zoo.query(
675 q="foo", i=1.0, n=1.0, b=0, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500676 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400677 request = zoo.query(
678 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500679 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500680
Joe Gregorioa98733f2011-09-16 10:12:28 -0400681 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800682 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar', er='two')
Joe Gregorio6804c7a2011-11-18 14:30:32 -0500683
684 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800685 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
686 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500687 self._check_query_types(request)
688
Craig Citro1e742822012-03-01 12:59:22 -0800689 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500690 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800691
Joe Gregorio13217952011-02-22 15:37:38 -0500692 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400693 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400694 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400695 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500696
Pat Ferated5b61bd2015-03-03 16:04:11 -0800697 parsed = urlparse(request.uri)
Joe Gregorioca876e42011-02-22 19:39:42 -0500698 q = parse_qs(parsed[4])
699 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400700 self.assertEqual(q['fields'], ['description'])
701
Joe Gregorio2467afa2012-06-20 12:21:25 -0400702 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400703 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400704 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400705 request = zoo.query(trace=None, fields='description')
706
Pat Ferated5b61bd2015-03-03 16:04:11 -0800707 parsed = urlparse(request.uri)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400708 q = parse_qs(parsed[4])
709 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400710
Joe Gregorioe08a1662011-12-07 09:48:22 -0500711 def test_model_added_query_parameters(self):
712 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400713 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500714 request = zoo.animals().get(name='Lion')
715
Pat Ferated5b61bd2015-03-03 16:04:11 -0800716 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500717 q = parse_qs(parsed[4])
718 self.assertEqual(q['alt'], ['json'])
719 self.assertEqual(request.headers['accept'], 'application/json')
720
721 def test_fallback_to_raw_model(self):
722 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400723 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500724 request = zoo.animals().getmedia(name='Lion')
725
Pat Ferated5b61bd2015-03-03 16:04:11 -0800726 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500727 q = parse_qs(parsed[4])
728 self.assertTrue('alt' not in q)
729 self.assertEqual(request.headers['accept'], '*/*')
730
Joe Gregoriof4153422011-03-18 22:45:18 -0400731 def test_patch(self):
732 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400733 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400734 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
735
736 self.assertEqual(request.method, 'PATCH')
737
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -0400738 def test_batch_request_from_discovery(self):
739 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
740 # zoo defines a batchPath
741 zoo = build('zoo', 'v1', http=self.http)
742 batch_request = zoo.new_batch_http_request()
743 self.assertEqual(batch_request._batch_uri,
744 "https://www.googleapis.com/batchZoo")
745
746 def test_batch_request_from_default(self):
747 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
748 # plus does not define a batchPath
749 plus = build('plus', 'v1', http=self.http)
750 batch_request = plus.new_batch_http_request()
751 self.assertEqual(batch_request._batch_uri,
752 "https://www.googleapis.com/batch")
753
Joe Gregoriof4153422011-03-18 22:45:18 -0400754 def test_tunnel_patch(self):
755 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400756 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400757 ({'status': '200'}, 'echo_request_headers_as_json'),
758 ])
759 http = tunnel_patch(http)
Takashi Matsuo30125122015-08-19 11:42:32 -0700760 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400761 resp = zoo.animals().patch(
762 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400763
764 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500765
Joe Gregorio7b70f432011-11-09 10:18:51 -0500766 def test_plus_resources(self):
767 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400768 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500769 self.assertTrue(getattr(plus, 'activities'))
770 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400771
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800772 def test_oauth2client_credentials(self):
773 credentials = mock.Mock(spec=GoogleCredentials)
774 credentials.create_scoped_required.return_value = False
Orest Bolohane92c9002014-05-30 11:15:43 -0700775
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800776 discovery = open(datafile('plus.json')).read()
777 service = build_from_document(discovery, credentials=credentials)
778 self.assertEqual(service._http, credentials.authorize.return_value)
Orest Bolohane92c9002014-05-30 11:15:43 -0700779
Jon Wayne Parrott85c2c6d2017-01-05 12:34:49 -0800780 def test_google_auth_credentials(self):
781 credentials = mock.Mock(spec=google.auth.credentials.Credentials)
782 discovery = open(datafile('plus.json')).read()
783 service = build_from_document(discovery, credentials=credentials)
784
785 self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp)
786 self.assertEqual(service._http.credentials, credentials)
787
788 def test_no_scopes_no_credentials(self):
789 # Zoo doesn't have scopes
790 discovery = open(datafile('zoo.json')).read()
791 service = build_from_document(discovery)
792 # Should be an ordinary httplib2.Http instance and not AuthorizedHttp.
793 self.assertIsInstance(service._http, httplib2.Http)
Orest Bolohane92c9002014-05-30 11:15:43 -0700794
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400795 def test_full_featured(self):
796 # Zoo should exercise all discovery facets
797 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500798 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400799 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400800 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500801
Joe Gregoriof4153422011-03-18 22:45:18 -0400802 request = zoo.animals().list(name='bat', projection="full")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800803 parsed = urlparse(request.uri)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400804 q = parse_qs(parsed[4])
805 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400806 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400807
Joe Gregorio3fada332011-01-07 17:07:45 -0500808 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500809 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400810 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500811 self.assertTrue(getattr(zoo, 'animals'))
812 request = zoo.my().favorites().list(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800813 parsed = urlparse(request.uri)
Joe Gregorio3fada332011-01-07 17:07:45 -0500814 q = parse_qs(parsed[4])
815 self.assertEqual(q['max-results'], ['5'])
816
Pat Feratec6050872015-03-03 18:24:59 -0800817 @unittest.skipIf(six.PY3, 'print is not a reserved name in Python 3')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400818 def test_methods_with_reserved_names(self):
819 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400820 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400821 self.assertTrue(getattr(zoo, 'animals'))
822 request = zoo.global_().print_().assert_(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800823 parsed = urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400824 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400825
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500826 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500827 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400828 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500829 self.assertTrue(getattr(zoo, 'query'))
830 request = zoo.query(q="foo")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800831 parsed = urlparse(request.uri)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500832 q = parse_qs(parsed[4])
833 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400834
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400835 def test_simple_media_uploads(self):
836 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400837 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400838 doc = getattr(zoo.animals().insert, '__doc__')
839 self.assertTrue('media_body' in doc)
840
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400841 def test_simple_media_upload_no_max_size_provided(self):
842 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400843 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400844 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
845 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800846 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400847
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400848 def test_simple_media_raise_correct_exceptions(self):
849 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400850 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400851
852 try:
853 zoo.animals().insert(media_body=datafile('smiley.png'))
854 self.fail("should throw exception if media is too large.")
855 except MediaUploadSizeError:
856 pass
857
858 try:
859 zoo.animals().insert(media_body=datafile('small.jpg'))
860 self.fail("should throw exception if mimetype is unacceptable.")
861 except UnacceptableMimeTypeError:
862 pass
863
864 def test_simple_media_good_upload(self):
865 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400866 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400867
868 request = zoo.animals().insert(media_body=datafile('small.png'))
869 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800870 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500871 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400872 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500873 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400874
Brian J. Watson38051ac2016-10-25 07:53:08 -0700875 def test_simple_media_unknown_mimetype(self):
876 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
877 zoo = build('zoo', 'v1', http=self.http)
878
879 try:
880 zoo.animals().insert(media_body=datafile('small-png'))
881 self.fail("should throw exception if mimetype is unknown.")
882 except UnknownFileType:
883 pass
884
885 request = zoo.animals().insert(media_body=datafile('small-png'),
886 media_mime_type='image/png')
887 self.assertEquals('image/png', request.headers['content-type'])
888 self.assertEquals(b'PNG', request.body[1:4])
889 assertUrisEqual(self,
890 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
891 request.uri)
892
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400893 def test_multipart_media_raise_correct_exceptions(self):
894 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400895 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400896
897 try:
898 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
899 self.fail("should throw exception if media is too large.")
900 except MediaUploadSizeError:
901 pass
902
903 try:
904 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
905 self.fail("should throw exception if mimetype is unacceptable.")
906 except UnacceptableMimeTypeError:
907 pass
908
909 def test_multipart_media_good_upload(self):
910 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400911 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400912
913 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400914 self.assertTrue(request.headers['content-type'].startswith(
915 'multipart/related'))
Phil Ruffwind26178fc2015-10-13 19:00:33 -0400916 with open(datafile('small.png'), 'rb') as f:
917 contents = f.read()
918 boundary = re.match(b'--=+([^=]+)', request.body).group(1)
919 self.assertEqual(
920 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
921 b'--===============' + boundary + b'==\n' +
922 b'Content-Type: application/json\n' +
923 b'MIME-Version: 1.0\n\n' +
924 b'{"data": {}}\n' +
925 b'--===============' + boundary + b'==\n' +
926 b'Content-Type: image/png\n' +
927 b'MIME-Version: 1.0\n' +
928 b'Content-Transfer-Encoding: binary\n\n' +
929 contents +
930 b'\n--===============' + boundary + b'==--')
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500931 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400932 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500933 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400934
935 def test_media_capable_method_without_media(self):
936 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400937 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400938
939 request = zoo.animals().insert(body={})
940 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400941
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500942 def test_resumable_multipart_media_good_upload(self):
943 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400944 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500945
946 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
947 request = zoo.animals().insert(media_body=media_upload, body={})
948 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500949 'application/json'))
950 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500951 self.assertEquals(media_upload, request.resumable)
952
953 self.assertEquals('image/png', request.resumable.mimetype())
954
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500955 self.assertNotEquals(request.body, None)
956 self.assertEquals(request.resumable_uri, None)
957
958 http = HttpMockSequence([
959 ({'status': '200',
960 'location': 'http://upload.example.com'}, ''),
961 ({'status': '308',
Matt Carroll94a53942016-12-20 13:56:43 -0800962 'location': 'http://upload.example.com/2'}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500963 ({'status': '308',
964 'location': 'http://upload.example.com/3',
Matt Carroll94a53942016-12-20 13:56:43 -0800965 'range': '0-12'}, ''),
966 ({'status': '308',
967 'location': 'http://upload.example.com/4',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500968 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500969 ({'status': '200'}, '{"foo": "bar"}'),
970 ])
971
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400972 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500973 self.assertEquals(None, body)
974 self.assertTrue(isinstance(status, MediaUploadProgress))
Matt Carroll94a53942016-12-20 13:56:43 -0800975 self.assertEquals(0, status.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500976
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500977 # Two requests should have been made and the resumable_uri should have been
978 # updated for each one.
979 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
Matt Carroll94a53942016-12-20 13:56:43 -0800980 self.assertEquals(media_upload, request.resumable)
981 self.assertEquals(0, request.resumable_progress)
982
983 # This next chuck call should upload the first chunk
984 status, body = request.next_chunk(http=http)
985 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500986 self.assertEquals(media_upload, request.resumable)
987 self.assertEquals(13, request.resumable_progress)
988
Matt Carroll94a53942016-12-20 13:56:43 -0800989 # This call will upload the next chunk
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400990 status, body = request.next_chunk(http=http)
Matt Carroll94a53942016-12-20 13:56:43 -0800991 self.assertEquals(request.resumable_uri, 'http://upload.example.com/4')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500992 self.assertEquals(media_upload.size()-1, request.resumable_progress)
993 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500994
995 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400996 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500997 self.assertEquals(body, {"foo": "bar"})
998 self.assertEquals(status, None)
999
1000
1001 def test_resumable_media_good_upload(self):
1002 """Not a multipart upload."""
1003 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001004 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001005
1006 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1007 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001008 self.assertEquals(media_upload, request.resumable)
1009
1010 self.assertEquals('image/png', request.resumable.mimetype())
1011
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001012 self.assertEquals(request.body, None)
1013 self.assertEquals(request.resumable_uri, None)
1014
1015 http = HttpMockSequence([
1016 ({'status': '200',
1017 'location': 'http://upload.example.com'}, ''),
1018 ({'status': '308',
1019 'location': 'http://upload.example.com/2',
1020 'range': '0-12'}, ''),
1021 ({'status': '308',
1022 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -05001023 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001024 ({'status': '200'}, '{"foo": "bar"}'),
1025 ])
1026
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001027 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001028 self.assertEquals(None, body)
1029 self.assertTrue(isinstance(status, MediaUploadProgress))
1030 self.assertEquals(13, status.resumable_progress)
1031
1032 # Two requests should have been made and the resumable_uri should have been
1033 # updated for each one.
1034 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
1035
1036 self.assertEquals(media_upload, request.resumable)
1037 self.assertEquals(13, request.resumable_progress)
1038
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001039 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001040 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -05001041 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001042 self.assertEquals(request.body, None)
1043
1044 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001045 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001046 self.assertEquals(body, {"foo": "bar"})
1047 self.assertEquals(status, None)
1048
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001049 def test_resumable_media_good_upload_from_execute(self):
1050 """Not a multipart upload."""
1051 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001052 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001053
1054 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1055 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -05001056 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -04001057 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -05001058 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001059
1060 http = HttpMockSequence([
1061 ({'status': '200',
1062 'location': 'http://upload.example.com'}, ''),
1063 ({'status': '308',
1064 'location': 'http://upload.example.com/2',
1065 'range': '0-12'}, ''),
1066 ({'status': '308',
1067 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -05001068 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001069 ({'status': '200'}, '{"foo": "bar"}'),
1070 ])
1071
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001072 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001073 self.assertEquals(body, {"foo": "bar"})
1074
1075 def test_resumable_media_fail_unknown_response_code_first_request(self):
1076 """Not a multipart upload."""
1077 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001078 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001079
1080 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1081 request = zoo.animals().insert(media_body=media_upload, body=None)
1082
1083 http = HttpMockSequence([
1084 ({'status': '400',
1085 'location': 'http://upload.example.com'}, ''),
1086 ])
1087
Joe Gregoriobaf04802013-03-01 12:27:06 -05001088 try:
1089 request.execute(http=http)
1090 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +09001091 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -05001092 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001093
1094 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
1095 """Not a multipart upload."""
1096 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001097 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001098
1099 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1100 request = zoo.animals().insert(media_body=media_upload, body=None)
1101
1102 http = HttpMockSequence([
1103 ({'status': '200',
1104 'location': 'http://upload.example.com'}, ''),
1105 ({'status': '400'}, ''),
1106 ])
1107
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001108 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001109 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001110
Joe Gregorio910b9b12012-06-12 09:36:30 -04001111 http = HttpMockSequence([
1112 ({'status': '308',
1113 'range': '0-5'}, ''),
1114 ({'status': '308',
1115 'range': '0-6'}, ''),
1116 ])
1117
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001118 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001119 self.assertEquals(status.resumable_progress, 7,
1120 'Should have first checked length and then tried to PUT more.')
1121 self.assertFalse(request._in_error_state)
1122
1123 # Put it back in an error state.
1124 http = HttpMockSequence([
1125 ({'status': '400'}, ''),
1126 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001127 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001128 self.assertTrue(request._in_error_state)
1129
1130 # Pretend the last request that 400'd actually succeeded.
1131 http = HttpMockSequence([
1132 ({'status': '200'}, '{"foo": "bar"}'),
1133 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001134 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001135 self.assertEqual(body, {'foo': 'bar'})
1136
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001137 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1138 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1139 zoo = build('zoo', 'v1', http=self.http)
1140
Pat Ferateed9affd2015-03-03 16:03:15 -08001141 # Set up a seekable stream and try to upload in single chunk.
Pat Ferate2b140222015-03-03 18:05:11 -08001142 fd = BytesIO(b'01234"56789"')
Pat Ferateed9affd2015-03-03 16:03:15 -08001143 media_upload = MediaIoBaseUpload(
1144 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001145
Pat Ferateed9affd2015-03-03 16:03:15 -08001146 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001147
Pat Ferateed9affd2015-03-03 16:03:15 -08001148 # The single chunk fails, restart at the right point.
1149 http = HttpMockSequence([
1150 ({'status': '200',
1151 'location': 'http://upload.example.com'}, ''),
1152 ({'status': '308',
1153 'location': 'http://upload.example.com/2',
1154 'range': '0-4'}, ''),
1155 ({'status': '200'}, 'echo_request_body'),
1156 ])
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001157
Pat Ferateed9affd2015-03-03 16:03:15 -08001158 body = request.execute(http=http)
1159 self.assertEqual('56789', body)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001160
1161 def test_media_io_base_stream_chunksize_resume(self):
1162 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1163 zoo = build('zoo', 'v1', http=self.http)
1164
Pat Ferateed9affd2015-03-03 16:03:15 -08001165 # Set up a seekable stream and try to upload in chunks.
Pat Ferate2b140222015-03-03 18:05:11 -08001166 fd = BytesIO(b'0123456789')
Pat Ferateed9affd2015-03-03 16:03:15 -08001167 media_upload = MediaIoBaseUpload(
1168 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
1169
1170 request = zoo.animals().insert(media_body=media_upload, body=None)
1171
1172 # The single chunk fails, pull the content sent out of the exception.
1173 http = HttpMockSequence([
1174 ({'status': '200',
1175 'location': 'http://upload.example.com'}, ''),
1176 ({'status': '400'}, 'echo_request_body'),
1177 ])
1178
Joe Gregorio5c120db2012-08-23 09:13:55 -04001179 try:
Pat Ferateed9affd2015-03-03 16:03:15 -08001180 body = request.execute(http=http)
1181 except HttpError as e:
Pat Ferate2b140222015-03-03 18:05:11 -08001182 self.assertEqual(b'01234', e.content)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001183
Joe Gregorio910b9b12012-06-12 09:36:30 -04001184 def test_resumable_media_handle_uploads_of_unknown_size(self):
1185 http = HttpMockSequence([
1186 ({'status': '200',
1187 'location': 'http://upload.example.com'}, ''),
1188 ({'status': '200'}, 'echo_request_headers_as_json'),
1189 ])
1190
1191 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001192 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001193
Joe Gregorio910b9b12012-06-12 09:36:30 -04001194 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001195 class IoBaseUnknownLength(MediaUpload):
1196 def chunksize(self):
1197 return 10
1198
1199 def mimetype(self):
1200 return 'image/png'
1201
1202 def size(self):
1203 return None
1204
1205 def resumable(self):
1206 return True
1207
1208 def getbytes(self, begin, length):
1209 return '0123456789'
1210
1211 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -04001212
1213 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001214 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001215 self.assertEqual(body, {
1216 'Content-Range': 'bytes 0-9/*',
1217 'Content-Length': '10',
1218 })
Joe Gregorio44454e42012-06-15 08:38:53 -04001219
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001220 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1221 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1222 zoo = build('zoo', 'v1', http=self.http)
1223
1224 class IoBaseHasStream(MediaUpload):
1225 def chunksize(self):
1226 return 10
1227
1228 def mimetype(self):
1229 return 'image/png'
1230
1231 def size(self):
1232 return None
1233
1234 def resumable(self):
1235 return True
1236
1237 def getbytes(self, begin, length):
1238 return '0123456789'
1239
1240 def has_stream(self):
1241 return True
1242
1243 def stream(self):
1244 raise NotImplementedError()
1245
1246 upload = IoBaseHasStream()
1247
1248 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001249
1250 sys.version_info = (2, 6, 5, 'final', 0)
1251
1252 request = zoo.animals().insert(media_body=upload, body=None)
1253
1254 # This should raise an exception because stream() will be called.
1255 http = HttpMockSequence([
1256 ({'status': '200',
1257 'location': 'http://upload.example.com'}, ''),
1258 ({'status': '200'}, 'echo_request_headers_as_json'),
1259 ])
1260
1261 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
1262
1263 sys.version_info = orig_version
1264
Joe Gregorio44454e42012-06-15 08:38:53 -04001265 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1266 http = HttpMockSequence([
1267 ({'status': '200',
1268 'location': 'http://upload.example.com'}, ''),
1269 ({'status': '200'}, 'echo_request_headers_as_json'),
1270 ])
1271
1272 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001273 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001274
Pat Ferate2b140222015-03-03 18:05:11 -08001275 fd = BytesIO(b'data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001276
1277 # Create an upload that doesn't know the full size of the media.
1278 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001279 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001280
1281 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001282 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001283 self.assertEqual(body, {
1284 'Content-Range': 'bytes 0-13/14',
1285 'Content-Length': '14',
1286 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001287
1288 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1289 http = HttpMockSequence([
1290 ({'status': '200',
1291 'location': 'http://upload.example.com'}, ''),
1292 ({'status': '400'}, ''),
1293 ])
1294
1295 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001296 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001297
1298 # Create an upload that doesn't know the full size of the media.
Pat Ferate2b140222015-03-03 18:05:11 -08001299 fd = BytesIO(b'data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001300
1301 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001302 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001303
1304 request = zoo.animals().insert(media_body=upload, body=None)
1305
1306 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001307 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001308
1309 http = HttpMockSequence([
1310 ({'status': '400',
1311 'range': '0-5'}, 'echo_request_headers_as_json'),
1312 ])
1313 try:
1314 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001315 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001316 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001317 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001318 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001319 'content-length': '0'
1320 }
INADA Naoki09157612015-03-25 01:51:03 +09001321 self.assertEqual(expected, json.loads(e.content.decode('utf-8')),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001322 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001323
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001324 def test_pickle(self):
1325 sorted_resource_keys = ['_baseUrl',
1326 '_developerKey',
1327 '_dynamic_attrs',
1328 '_http',
1329 '_model',
1330 '_requestBuilder',
1331 '_resourceDesc',
1332 '_rootDesc',
1333 '_schema',
1334 'animals',
1335 'global_',
1336 'load',
1337 'loadNoTemplate',
1338 'my',
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001339 'new_batch_http_request',
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001340 'query',
1341 'scopedAnimals']
1342
1343 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1344 zoo = build('zoo', 'v1', http=http)
1345 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1346
1347 pickled_zoo = pickle.dumps(zoo)
1348 new_zoo = pickle.loads(pickled_zoo)
1349 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1350 self.assertTrue(hasattr(new_zoo, 'animals'))
1351 self.assertTrue(callable(new_zoo.animals))
1352 self.assertTrue(hasattr(new_zoo, 'global_'))
1353 self.assertTrue(callable(new_zoo.global_))
1354 self.assertTrue(hasattr(new_zoo, 'load'))
1355 self.assertTrue(callable(new_zoo.load))
1356 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1357 self.assertTrue(callable(new_zoo.loadNoTemplate))
1358 self.assertTrue(hasattr(new_zoo, 'my'))
1359 self.assertTrue(callable(new_zoo.my))
1360 self.assertTrue(hasattr(new_zoo, 'query'))
1361 self.assertTrue(callable(new_zoo.query))
1362 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1363 self.assertTrue(callable(new_zoo.scopedAnimals))
1364
Joe Gregorio003b6e42013-02-13 15:42:19 -05001365 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001366 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1367 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1368 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1369 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1370 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1371 # _http, _model and _schema won't be equal since we will get new
1372 # instances upon un-pickling
1373
1374 def _dummy_zoo_request(self):
1375 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1376 zoo_contents = fh.read()
1377
1378 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1379 {'api': 'zoo', 'apiVersion': 'v1'})
1380 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001381 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1382 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001383
Igor Maravić22435292017-01-19 22:28:22 +01001384 http = build_http()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001385 original_request = http.request
1386 def wrapped_request(uri, method='GET', *args, **kwargs):
1387 if uri == zoo_uri:
1388 return httplib2.Response({'status': '200'}), zoo_contents
1389 return original_request(uri, method=method, *args, **kwargs)
1390 http.request = wrapped_request
1391 return http
1392
1393 def _dummy_token(self):
1394 access_token = 'foo'
1395 client_id = 'some_client_id'
1396 client_secret = 'cOuDdkfjxxnv+'
1397 refresh_token = '1/0/a.df219fjls0'
1398 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001399 user_agent = 'refresh_checker/1.0'
1400 return OAuth2Credentials(
1401 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001402 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001403 user_agent)
1404
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001405 def test_pickle_with_credentials(self):
1406 credentials = self._dummy_token()
1407 http = self._dummy_zoo_request()
1408 http = credentials.authorize(http)
1409 self.assertTrue(hasattr(http.request, 'credentials'))
1410
1411 zoo = build('zoo', 'v1', http=http)
1412 pickled_zoo = pickle.dumps(zoo)
1413 new_zoo = pickle.loads(pickled_zoo)
1414 self.assertEqual(sorted(zoo.__dict__.keys()),
1415 sorted(new_zoo.__dict__.keys()))
1416 new_http = new_zoo._http
1417 self.assertFalse(hasattr(new_http.request, 'credentials'))
1418
andrewnestera4a44cf2017-03-31 16:09:31 +03001419 def test_resumable_media_upload_no_content(self):
1420 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1421 zoo = build('zoo', 'v1', http=self.http)
1422
1423 media_upload = MediaFileUpload(datafile('empty'), resumable=True)
1424 request = zoo.animals().insert(media_body=media_upload, body=None)
1425
1426 self.assertEquals(media_upload, request.resumable)
1427 self.assertEquals(request.body, None)
1428 self.assertEquals(request.resumable_uri, None)
1429
1430 http = HttpMockSequence([
1431 ({'status': '200',
1432 'location': 'http://upload.example.com'}, ''),
1433 ({'status': '308',
1434 'location': 'http://upload.example.com/2',
1435 'range': '0-0'}, ''),
1436 ])
1437
1438 status, body = request.next_chunk(http=http)
1439 self.assertEquals(None, body)
1440 self.assertTrue(isinstance(status, MediaUploadProgress))
1441 self.assertEquals(0, status.progress())
1442
Joe Gregorio708388c2012-06-15 13:43:04 -04001443
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001444class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001445
Joe Gregorio3c676f92011-07-25 10:38:14 -04001446 def test_next_successful_none_on_no_next_page_token(self):
1447 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001448 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001449 request = tasks.tasklists().list()
1450 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1451
Son Dinh2a9a2132015-07-23 16:30:56 +00001452 def test_next_successful_none_on_empty_page_token(self):
1453 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
1454 tasks = build('tasks', 'v1', http=self.http)
1455 request = tasks.tasklists().list()
1456 next_request = tasks.tasklists().list_next(
1457 request, {'nextPageToken': ''})
1458 self.assertEqual(None, next_request)
1459
Joe Gregorio3c676f92011-07-25 10:38:14 -04001460 def test_next_successful_with_next_page_token(self):
1461 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001462 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001463 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001464 next_request = tasks.tasklists().list_next(
1465 request, {'nextPageToken': '123abc'})
Pat Ferated5b61bd2015-03-03 16:04:11 -08001466 parsed = list(urlparse(next_request.uri))
Joe Gregorio3c676f92011-07-25 10:38:14 -04001467 q = parse_qs(parsed[4])
1468 self.assertEqual(q['pageToken'][0], '123abc')
1469
Thomas Coffee20af04d2017-02-10 15:24:44 -08001470 def test_next_successful_with_next_page_token_alternate_name(self):
1471 self.http = HttpMock(datafile('bigquery.json'), {'status': '200'})
1472 bigquery = build('bigquery', 'v2', http=self.http)
1473 request = bigquery.tabledata().list(datasetId='', projectId='', tableId='')
1474 next_request = bigquery.tabledata().list_next(
1475 request, {'pageToken': '123abc'})
1476 parsed = list(urlparse(next_request.uri))
1477 q = parse_qs(parsed[4])
1478 self.assertEqual(q['pageToken'][0], '123abc')
1479
1480 def test_next_successful_with_next_page_token_in_body(self):
1481 self.http = HttpMock(datafile('logging.json'), {'status': '200'})
1482 logging = build('logging', 'v2', http=self.http)
1483 request = logging.entries().list(body={})
1484 next_request = logging.entries().list_next(
1485 request, {'nextPageToken': '123abc'})
1486 body = JsonModel().deserialize(next_request.body)
1487 self.assertEqual(body['pageToken'], '123abc')
1488
Joe Gregorio555f33c2011-08-19 14:56:07 -04001489 def test_next_with_method_with_no_properties(self):
1490 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001491 service = build('latitude', 'v1', http=self.http)
Thomas Coffee20af04d2017-02-10 15:24:44 -08001492 service.currentLocation().get()
1493
1494 def test_next_nonexistent_with_no_next_page_token(self):
1495 self.http = HttpMock(datafile('drive.json'), {'status': '200'})
1496 drive = build('drive', 'v3', http=self.http)
1497 drive.changes().watch(body={})
1498 self.assertFalse(callable(getattr(drive.changes(), 'watch_next', None)))
1499
1500 def test_next_successful_with_next_page_token_required(self):
1501 self.http = HttpMock(datafile('drive.json'), {'status': '200'})
1502 drive = build('drive', 'v3', http=self.http)
1503 request = drive.changes().list(pageToken='startPageToken')
1504 next_request = drive.changes().list_next(
1505 request, {'nextPageToken': '123abc'})
1506 parsed = list(urlparse(next_request.uri))
1507 q = parse_qs(parsed[4])
1508 self.assertEqual(q['pageToken'][0], '123abc')
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001509
Joe Gregorioa98733f2011-09-16 10:12:28 -04001510
Joe Gregorio708388c2012-06-15 13:43:04 -04001511class MediaGet(unittest.TestCase):
1512
1513 def test_get_media(self):
1514 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001515 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001516 request = zoo.animals().get_media(name='Lion')
1517
Pat Ferated5b61bd2015-03-03 16:04:11 -08001518 parsed = urlparse(request.uri)
Joe Gregorio708388c2012-06-15 13:43:04 -04001519 q = parse_qs(parsed[4])
1520 self.assertEqual(q['alt'], ['media'])
1521 self.assertEqual(request.headers['accept'], '*/*')
1522
1523 http = HttpMockSequence([
1524 ({'status': '200'}, 'standing in for media'),
1525 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001526 response = request.execute(http=http)
INADA Naoki09157612015-03-25 01:51:03 +09001527 self.assertEqual(b'standing in for media', response)
Joe Gregorio708388c2012-06-15 13:43:04 -04001528
1529
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001530if __name__ == '__main__':
1531 unittest.main()