blob: 7b34606e2f0979b1f0fd82550140c754763b9196 [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
John Asmuth864311d2014-04-24 15:46:08 -040044from googleapiclient.discovery import _fix_up_media_upload
45from googleapiclient.discovery import _fix_up_method_description
46from googleapiclient.discovery import _fix_up_parameters
Craig Citro7ee535d2015-02-23 10:11:14 -080047from googleapiclient.discovery import _urljoin
John Asmuth864311d2014-04-24 15:46:08 -040048from googleapiclient.discovery import build
49from googleapiclient.discovery import build_from_document
50from googleapiclient.discovery import DISCOVERY_URI
51from googleapiclient.discovery import key2param
52from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
Brian J. Watson38051ac2016-10-25 07:53:08 -070053from googleapiclient.discovery import MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE
John Asmuth864311d2014-04-24 15:46:08 -040054from googleapiclient.discovery import ResourceMethodParameters
55from googleapiclient.discovery import STACK_QUERY_PARAMETERS
56from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Takashi Matsuo30125122015-08-19 11:42:32 -070057from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE
58from googleapiclient.discovery_cache.base import Cache
John Asmuth864311d2014-04-24 15:46:08 -040059from googleapiclient.errors import HttpError
60from googleapiclient.errors import InvalidJsonError
61from googleapiclient.errors import MediaUploadSizeError
62from googleapiclient.errors import ResumableUploadError
63from googleapiclient.errors import UnacceptableMimeTypeError
Takashi Matsuo3772f9d2015-09-04 12:25:55 -070064from googleapiclient.errors import UnknownApiNameOrVersion
Brian J. Watson38051ac2016-10-25 07:53:08 -070065from googleapiclient.errors import UnknownFileType
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -040066from googleapiclient.http import BatchHttpRequest
John Asmuth864311d2014-04-24 15:46:08 -040067from googleapiclient.http import HttpMock
68from googleapiclient.http import HttpMockSequence
69from googleapiclient.http import MediaFileUpload
70from googleapiclient.http import MediaIoBaseUpload
71from googleapiclient.http import MediaUpload
72from googleapiclient.http import MediaUploadProgress
73from googleapiclient.http import tunnel_patch
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080074from oauth2client import GOOGLE_TOKEN_URI
Joe Gregoriodc106fc2012-11-20 14:30:14 -050075from oauth2client.client import OAuth2Credentials
Joe Gregorio79daca02013-03-29 16:25:52 -040076
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -070077try:
78 from oauth2client import util
79except ImportError:
80 from oauth2client import _helpers as util
81
Joe Gregoriodc106fc2012-11-20 14:30:14 -050082import uritemplate
83
Joe Gregoriocb8103d2011-02-11 23:20:52 -050084
85DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
86
Joe Gregorioa98733f2011-09-16 10:12:28 -040087
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050088def assertUrisEqual(testcase, expected, actual):
89 """Test that URIs are the same, up to reordering of query parameters."""
Pat Ferated5b61bd2015-03-03 16:04:11 -080090 expected = urlparse(expected)
91 actual = urlparse(actual)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050092 testcase.assertEqual(expected.scheme, actual.scheme)
93 testcase.assertEqual(expected.netloc, actual.netloc)
94 testcase.assertEqual(expected.path, actual.path)
95 testcase.assertEqual(expected.params, actual.params)
96 testcase.assertEqual(expected.fragment, actual.fragment)
97 expected_query = parse_qs(expected.query)
98 actual_query = parse_qs(actual.query)
INADA Naokid898a372015-03-04 03:52:46 +090099 for name in list(expected_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500100 testcase.assertEqual(expected_query[name], actual_query[name])
INADA Naokid898a372015-03-04 03:52:46 +0900101 for name in list(actual_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500102 testcase.assertEqual(expected_query[name], actual_query[name])
103
104
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500105def datafile(filename):
106 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400107
108
Joe Gregorio504a17f2012-12-07 14:14:26 -0500109class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800110
Joe Gregorio504a17f2012-12-07 14:14:26 -0500111 def test_retries(self):
John Asmuth864311d2014-04-24 15:46:08 -0400112 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
Joe Gregorio504a17f2012-12-07 14:14:26 -0500113 self.assertEqual(1, httplib2.RETRIES)
114
115
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400116class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800117
118 def setUp(self):
119 with open(datafile('zoo.json'), 'r') as fh:
Craig Citro6ae34d72014-08-18 23:10:09 -0700120 self.zoo_root_desc = json.loads(fh.read())
Daniel Hermesc2113242013-02-27 10:16:13 -0800121 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800122 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
123 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Daniel Hermesc2113242013-02-27 10:16:13 -0800124
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400125 def test_key2param(self):
126 self.assertEqual('max_results', key2param('max-results'))
127 self.assertEqual('x007_bond', key2param('007-bond'))
128
Daniel Hermesc2113242013-02-27 10:16:13 -0800129 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
130 self.assertEqual(method_desc['httpMethod'], http_method)
131
132 method_desc_copy = copy.deepcopy(method_desc)
133 self.assertEqual(method_desc, method_desc_copy)
134
135 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
136
137 self.assertNotEqual(method_desc, method_desc_copy)
138
139 for param_name in STACK_QUERY_PARAMETERS:
140 self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
141 parameters[param_name])
142
INADA Naokid898a372015-03-04 03:52:46 +0900143 for param_name, value in six.iteritems(root_desc.get('parameters', {})):
Daniel Hermesc2113242013-02-27 10:16:13 -0800144 self.assertEqual(value, parameters[param_name])
145
146 return parameters
147
148 def test_fix_up_parameters_get(self):
149 parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
150 'GET', self.zoo_root_desc)
151 # Since http_method is 'GET'
INADA Naoki0bceb332014-08-20 15:27:52 +0900152 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800153
154 def test_fix_up_parameters_insert(self):
155 parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
156 'POST', self.zoo_root_desc)
157 body = {
158 'description': 'The request body.',
159 'type': 'object',
160 'required': True,
161 '$ref': 'Animal',
162 }
163 self.assertEqual(parameters['body'], body)
164
165 def test_fix_up_parameters_check_body(self):
166 dummy_root_desc = {}
167 no_payload_http_method = 'DELETE'
168 with_payload_http_method = 'PUT'
169
170 invalid_method_desc = {'response': 'Who cares'}
171 valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
172
173 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
174 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900175 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800176
177 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
178 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900179 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800180
181 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
182 with_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900183 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800184
185 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
186 with_payload_http_method)
187 body = {
188 'description': 'The request body.',
189 'type': 'object',
190 'required': True,
191 'key1': 'value1',
192 'key2': 'value2',
193 }
194 self.assertEqual(parameters['body'], body)
195
196 def _base_fix_up_method_description_test(
197 self, method_desc, initial_parameters, final_parameters,
198 final_accept, final_max_size, final_media_path_url):
199 fake_root_desc = {'rootUrl': 'http://root/',
200 'servicePath': 'fake/'}
201 fake_path_url = 'fake-path/'
202
203 accept, max_size, media_path_url = _fix_up_media_upload(
204 method_desc, fake_root_desc, fake_path_url, initial_parameters)
205 self.assertEqual(accept, final_accept)
206 self.assertEqual(max_size, final_max_size)
207 self.assertEqual(media_path_url, final_media_path_url)
208 self.assertEqual(initial_parameters, final_parameters)
209
210 def test_fix_up_media_upload_no_initial_invalid(self):
211 invalid_method_desc = {'response': 'Who cares'}
212 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
213 [], 0, None)
214
215 def test_fix_up_media_upload_no_initial_valid_minimal(self):
216 valid_method_desc = {'mediaUpload': {'accept': []}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700217 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
218 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800219 self._base_fix_up_method_description_test(
220 valid_method_desc, {}, final_parameters, [], 0,
221 'http://root/upload/fake/fake-path/')
222
223 def test_fix_up_media_upload_no_initial_valid_full(self):
224 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
Brian J. Watson38051ac2016-10-25 07:53:08 -0700225 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
226 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800227 ten_gb = 10 * 2**30
228 self._base_fix_up_method_description_test(
229 valid_method_desc, {}, final_parameters, ['*/*'],
230 ten_gb, 'http://root/upload/fake/fake-path/')
231
232 def test_fix_up_media_upload_with_initial_invalid(self):
233 invalid_method_desc = {'response': 'Who cares'}
234 initial_parameters = {'body': {}}
235 self._base_fix_up_method_description_test(
236 invalid_method_desc, initial_parameters,
237 initial_parameters, [], 0, None)
238
239 def test_fix_up_media_upload_with_initial_valid_minimal(self):
240 valid_method_desc = {'mediaUpload': {'accept': []}}
241 initial_parameters = {'body': {}}
242 final_parameters = {'body': {'required': False},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700243 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
244 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800245 self._base_fix_up_method_description_test(
246 valid_method_desc, initial_parameters, final_parameters, [], 0,
247 'http://root/upload/fake/fake-path/')
248
249 def test_fix_up_media_upload_with_initial_valid_full(self):
250 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
251 initial_parameters = {'body': {}}
252 final_parameters = {'body': {'required': False},
Brian J. Watson38051ac2016-10-25 07:53:08 -0700253 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE,
254 'media_mime_type': MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE}
Daniel Hermesc2113242013-02-27 10:16:13 -0800255 ten_gb = 10 * 2**30
256 self._base_fix_up_method_description_test(
257 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
258 ten_gb, 'http://root/upload/fake/fake-path/')
259
260 def test_fix_up_method_description_get(self):
261 result = _fix_up_method_description(self.zoo_get_method_desc,
262 self.zoo_root_desc)
263 path_url = 'query'
264 http_method = 'GET'
265 method_id = 'bigquery.query'
266 accept = []
INADA Naoki0bceb332014-08-20 15:27:52 +0900267 max_size = 0
Daniel Hermesc2113242013-02-27 10:16:13 -0800268 media_path_url = None
269 self.assertEqual(result, (path_url, http_method, method_id, accept,
270 max_size, media_path_url))
271
272 def test_fix_up_method_description_insert(self):
273 result = _fix_up_method_description(self.zoo_insert_method_desc,
274 self.zoo_root_desc)
275 path_url = 'animals'
276 http_method = 'POST'
277 method_id = 'zoo.animals.insert'
278 accept = ['image/png']
INADA Naoki0bceb332014-08-20 15:27:52 +0900279 max_size = 1024
Daniel Hermesc2113242013-02-27 10:16:13 -0800280 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
281 self.assertEqual(result, (path_url, http_method, method_id, accept,
282 max_size, media_path_url))
283
Craig Citro7ee535d2015-02-23 10:11:14 -0800284 def test_urljoin(self):
285 # We want to exhaustively test various URL combinations.
286 simple_bases = ['https://www.googleapis.com', 'https://www.googleapis.com/']
287 long_urls = ['foo/v1/bar:custom?alt=json', '/foo/v1/bar:custom?alt=json']
288
289 long_bases = [
290 'https://www.googleapis.com/foo/v1',
291 'https://www.googleapis.com/foo/v1/',
292 ]
293 simple_urls = ['bar:custom?alt=json', '/bar:custom?alt=json']
294
295 final_url = 'https://www.googleapis.com/foo/v1/bar:custom?alt=json'
296 for base, url in itertools.product(simple_bases, long_urls):
297 self.assertEqual(final_url, _urljoin(base, url))
298 for base, url in itertools.product(long_bases, simple_urls):
299 self.assertEqual(final_url, _urljoin(base, url))
300
301
Daniel Hermes954e1242013-02-28 09:28:37 -0800302 def test_ResourceMethodParameters_zoo_get(self):
303 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
304
305 param_types = {'a': 'any',
306 'b': 'boolean',
307 'e': 'string',
308 'er': 'string',
309 'i': 'integer',
310 'n': 'number',
311 'o': 'object',
312 'q': 'string',
313 'rr': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900314 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800315 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
316 self.assertEqual(parameters.required_params, [])
317 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
318 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
319 self.assertEqual(sorted(parameters.query_params),
320 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
321 self.assertEqual(parameters.path_params, set())
322 self.assertEqual(parameters.param_types, param_types)
323 enum_params = {'e': ['foo', 'bar'],
324 'er': ['one', 'two', 'three']}
325 self.assertEqual(parameters.enum_params, enum_params)
326
327 def test_ResourceMethodParameters_zoo_animals_patch(self):
328 method_desc = self.zoo_animals_resource['methods']['patch']
329 parameters = ResourceMethodParameters(method_desc)
330
331 param_types = {'name': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900332 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800333 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
334 self.assertEqual(parameters.required_params, ['name'])
335 self.assertEqual(parameters.repeated_params, [])
336 self.assertEqual(parameters.pattern_params, {})
337 self.assertEqual(parameters.query_params, [])
338 self.assertEqual(parameters.path_params, set(['name']))
339 self.assertEqual(parameters.param_types, param_types)
340 self.assertEqual(parameters.enum_params, {})
341
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400342
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500343class DiscoveryErrors(unittest.TestCase):
344
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400345 def test_tests_should_be_run_with_strict_positional_enforcement(self):
346 try:
347 plus = build('plus', 'v1', None)
348 self.fail("should have raised a TypeError exception over missing http=.")
349 except TypeError:
350 pass
351
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500352 def test_failed_to_parse_discovery_json(self):
353 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
354 try:
Takashi Matsuo30125122015-08-19 11:42:32 -0700355 plus = build('plus', 'v1', http=self.http, cache_discovery=False)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500356 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500357 except InvalidJsonError:
358 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500359
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700360 def test_unknown_api_name_or_version(self):
361 http = HttpMockSequence([
362 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Ethan Bao12b7cd32016-03-14 14:25:10 -0700363 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700364 ])
365 with self.assertRaises(UnknownApiNameOrVersion):
366 plus = build('plus', 'v1', http=http, cache_discovery=False)
367
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500368
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100369class DiscoveryFromDocument(unittest.TestCase):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400370
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100371 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400372 discovery = open(datafile('plus.json')).read()
Joe Gregorio7b70f432011-11-09 10:18:51 -0500373 plus = build_from_document(discovery, base="https://www.googleapis.com/")
374 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500375 self.assertTrue(hasattr(plus, 'activities'))
376
377 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400378 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700379 discovery = json.loads(discovery)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500380 plus = build_from_document(discovery, base="https://www.googleapis.com/")
381 self.assertTrue(plus is not None)
382 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400383
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100384 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400385 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400386
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100387 base = "https://www.example.com/"
Joe Gregorio7b70f432011-11-09 10:18:51 -0500388 plus = build_from_document(discovery, base=base)
Joe Gregorioa2838152012-07-16 11:52:17 -0400389 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100390
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700391 def test_building_with_optional_http(self):
392 discovery = open(datafile('plus.json')).read()
393 plus = build_from_document(discovery, base="https://www.googleapis.com/")
394 self.assertTrue(isinstance(plus._http, httplib2.Http))
395
396 def test_building_with_explicit_http(self):
397 http = HttpMock()
398 discovery = open(datafile('plus.json')).read()
399 plus = build_from_document(
400 discovery, base="https://www.googleapis.com/", http=http)
401 self.assertEquals(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100402
Joe Gregorioa98733f2011-09-16 10:12:28 -0400403class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400404 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700405 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400406
Joe Gregorio583d9e42011-09-16 15:54:15 -0400407 def tearDown(self):
408 os.environ = self.old_environ
409
410 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400411 # build() will raise an HttpError on a 400, use this to pick the request uri
412 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400413 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400414 try:
415 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400416 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400417 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400418 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400419 discoveryServiceUrl='http://example.com')
420 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900421 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400422 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400423
Joe Gregorio583d9e42011-09-16 15:54:15 -0400424 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400425 # build() will raise an HttpError on a 400, use this to pick the request uri
426 # out of the raised exception.
427 try:
428 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400429 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400430 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400431 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400432 discoveryServiceUrl='http://example.com')
433 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900434 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400435 self.assertEqual(e.uri, 'http://example.com')
436
Ethan Bao12b7cd32016-03-14 14:25:10 -0700437 def test_discovery_loading_from_v2_discovery_uri(self):
438 http = HttpMockSequence([
439 ({'status': '404'}, 'Not found'),
440 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
441 ])
442 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
443 self.assertTrue(hasattr(zoo, 'animals'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400444
Takashi Matsuo30125122015-08-19 11:42:32 -0700445class DiscoveryFromAppEngineCache(unittest.TestCase):
446 def test_appengine_memcache(self):
447 # Hack module import
448 self.orig_import = __import__
449 self.mocked_api = mock.MagicMock()
450
eesheeshc6425a02016-02-12 15:07:06 +0000451 def import_mock(name, *args, **kwargs):
Takashi Matsuo30125122015-08-19 11:42:32 -0700452 if name == 'google.appengine.api':
453 return self.mocked_api
eesheeshc6425a02016-02-12 15:07:06 +0000454 return self.orig_import(name, *args, **kwargs)
Takashi Matsuo30125122015-08-19 11:42:32 -0700455
456 import_fullname = '__builtin__.__import__'
457 if sys.version_info[0] >= 3:
458 import_fullname = 'builtins.__import__'
459
460 with mock.patch(import_fullname, side_effect=import_mock):
461 namespace = 'google-api-client'
462 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
463
464 self.mocked_api.memcache.get.return_value = None
465
466 plus = build('plus', 'v1', http=self.http)
467
468 # memcache.get is called once
469 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
470 self.mocked_api.memcache.get.assert_called_once_with(url,
471 namespace=namespace)
472
473 # memcache.set is called once
474 with open(datafile('plus.json')) as f:
475 content = f.read()
476 self.mocked_api.memcache.set.assert_called_once_with(
477 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace)
478
479 # Returns the cached content this time.
480 self.mocked_api.memcache.get.return_value = content
481
482 # Make sure the contents are returned from the cache.
483 # (Otherwise it should through an error)
484 self.http = HttpMock(None, {'status': '200'})
485
486 plus = build('plus', 'v1', http=self.http)
487
488 # memcache.get is called twice
489 self.mocked_api.memcache.get.assert_has_calls(
490 [mock.call(url, namespace=namespace),
491 mock.call(url, namespace=namespace)])
492
493 # memcahce.set is called just once
494 self.mocked_api.memcache.set.assert_called_once_with(
495 url, content, time=DISCOVERY_DOC_MAX_AGE,namespace=namespace)
496
497
498class DictCache(Cache):
499 def __init__(self):
500 self.d = {}
501 def get(self, url):
502 return self.d.get(url, None)
503 def set(self, url, content):
504 self.d[url] = content
505 def contains(self, url):
506 return url in self.d
507
508
509class DiscoveryFromFileCache(unittest.TestCase):
510 def test_file_based_cache(self):
511 cache = mock.Mock(wraps=DictCache())
Jon Wayne Parrott36d4e1b2016-10-17 13:31:33 -0700512 with mock.patch('googleapiclient.discovery_cache.autodetect',
513 return_value=cache):
Takashi Matsuo30125122015-08-19 11:42:32 -0700514 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
515
516 plus = build('plus', 'v1', http=self.http)
517
518 # cache.get is called once
519 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
520 cache.get.assert_called_once_with(url)
521
522 # cache.set is called once
523 with open(datafile('plus.json')) as f:
524 content = f.read()
525 cache.set.assert_called_once_with(url, content)
526
527 # Make sure there is a cache entry for the plus v1 discovery doc.
528 self.assertTrue(cache.contains(url))
529
530 # Make sure the contents are returned from the cache.
531 # (Otherwise it should through an error)
532 self.http = HttpMock(None, {'status': '200'})
533
534 plus = build('plus', 'v1', http=self.http)
535
536 # cache.get is called twice
537 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
538
539 # cahce.set is called just once
540 cache.set.assert_called_once_with(url, content)
541
542
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400543class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400544
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400545 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500546 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400547 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400548
549 # Missing required parameters
550 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500551 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400552 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900553 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400554 self.assertTrue('Missing' in str(e))
555
Joe Gregorio2467afa2012-06-20 12:21:25 -0400556 # Missing required parameters even if supplied as None.
557 try:
558 plus.activities().list(collection=None, userId=None)
559 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900560 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400561 self.assertTrue('Missing' in str(e))
562
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400563 # Parameter doesn't match regex
564 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500565 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400566 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900567 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500568 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400569
570 # Unexpected parameter
571 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500572 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400573 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900574 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400575 self.assertTrue('unexpected' in str(e))
576
Joe Gregoriobee86832011-02-22 10:00:19 -0500577 def _check_query_types(self, request):
Pat Ferated5b61bd2015-03-03 16:04:11 -0800578 parsed = urlparse(request.uri)
Joe Gregoriobee86832011-02-22 10:00:19 -0500579 q = parse_qs(parsed[4])
580 self.assertEqual(q['q'], ['foo'])
581 self.assertEqual(q['i'], ['1'])
582 self.assertEqual(q['n'], ['1.0'])
583 self.assertEqual(q['b'], ['false'])
584 self.assertEqual(q['a'], ['[1, 2, 3]'])
585 self.assertEqual(q['o'], ['{\'a\': 1}'])
586 self.assertEqual(q['e'], ['bar'])
587
588 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400589 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400590 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500591
Joe Gregorioa98733f2011-09-16 10:12:28 -0400592 request = zoo.query(
593 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 -0500594 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400595 request = zoo.query(
596 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500597 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500598
Joe Gregorioa98733f2011-09-16 10:12:28 -0400599 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800600 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 -0500601
602 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800603 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
604 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500605 self._check_query_types(request)
606
Craig Citro1e742822012-03-01 12:59:22 -0800607 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500608 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800609
Joe Gregorio13217952011-02-22 15:37:38 -0500610 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400611 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400612 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400613 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500614
Pat Ferated5b61bd2015-03-03 16:04:11 -0800615 parsed = urlparse(request.uri)
Joe Gregorioca876e42011-02-22 19:39:42 -0500616 q = parse_qs(parsed[4])
617 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400618 self.assertEqual(q['fields'], ['description'])
619
Joe Gregorio2467afa2012-06-20 12:21:25 -0400620 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400621 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400622 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400623 request = zoo.query(trace=None, fields='description')
624
Pat Ferated5b61bd2015-03-03 16:04:11 -0800625 parsed = urlparse(request.uri)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400626 q = parse_qs(parsed[4])
627 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400628
Joe Gregorioe08a1662011-12-07 09:48:22 -0500629 def test_model_added_query_parameters(self):
630 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400631 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500632 request = zoo.animals().get(name='Lion')
633
Pat Ferated5b61bd2015-03-03 16:04:11 -0800634 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500635 q = parse_qs(parsed[4])
636 self.assertEqual(q['alt'], ['json'])
637 self.assertEqual(request.headers['accept'], 'application/json')
638
639 def test_fallback_to_raw_model(self):
640 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400641 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500642 request = zoo.animals().getmedia(name='Lion')
643
Pat Ferated5b61bd2015-03-03 16:04:11 -0800644 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500645 q = parse_qs(parsed[4])
646 self.assertTrue('alt' not in q)
647 self.assertEqual(request.headers['accept'], '*/*')
648
Joe Gregoriof4153422011-03-18 22:45:18 -0400649 def test_patch(self):
650 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400651 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400652 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
653
654 self.assertEqual(request.method, 'PATCH')
655
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -0400656 def test_batch_request_from_discovery(self):
657 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
658 # zoo defines a batchPath
659 zoo = build('zoo', 'v1', http=self.http)
660 batch_request = zoo.new_batch_http_request()
661 self.assertEqual(batch_request._batch_uri,
662 "https://www.googleapis.com/batchZoo")
663
664 def test_batch_request_from_default(self):
665 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
666 # plus does not define a batchPath
667 plus = build('plus', 'v1', http=self.http)
668 batch_request = plus.new_batch_http_request()
669 self.assertEqual(batch_request._batch_uri,
670 "https://www.googleapis.com/batch")
671
Joe Gregoriof4153422011-03-18 22:45:18 -0400672 def test_tunnel_patch(self):
673 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400674 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400675 ({'status': '200'}, 'echo_request_headers_as_json'),
676 ])
677 http = tunnel_patch(http)
Takashi Matsuo30125122015-08-19 11:42:32 -0700678 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400679 resp = zoo.animals().patch(
680 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400681
682 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500683
Joe Gregorio7b70f432011-11-09 10:18:51 -0500684 def test_plus_resources(self):
685 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400686 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500687 self.assertTrue(getattr(plus, 'activities'))
688 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400689
Orest Bolohane92c9002014-05-30 11:15:43 -0700690 def test_credentials(self):
691 class CredentialsMock:
692 def create_scoped_required(self):
693 return False
694
695 def authorize(self, http):
696 http.orest = True
697
698 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
699 build('plus', 'v1', http=self.http, credentials=None)
700 self.assertFalse(hasattr(self.http, 'orest'))
701 build('plus', 'v1', http=self.http, credentials=CredentialsMock())
702 self.assertTrue(hasattr(self.http, 'orest'))
703
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400704 def test_full_featured(self):
705 # Zoo should exercise all discovery facets
706 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500707 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400708 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400709 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500710
Joe Gregoriof4153422011-03-18 22:45:18 -0400711 request = zoo.animals().list(name='bat', projection="full")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800712 parsed = urlparse(request.uri)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400713 q = parse_qs(parsed[4])
714 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400715 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400716
Joe Gregorio3fada332011-01-07 17:07:45 -0500717 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500718 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400719 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500720 self.assertTrue(getattr(zoo, 'animals'))
721 request = zoo.my().favorites().list(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800722 parsed = urlparse(request.uri)
Joe Gregorio3fada332011-01-07 17:07:45 -0500723 q = parse_qs(parsed[4])
724 self.assertEqual(q['max-results'], ['5'])
725
Pat Feratec6050872015-03-03 18:24:59 -0800726 @unittest.skipIf(six.PY3, 'print is not a reserved name in Python 3')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400727 def test_methods_with_reserved_names(self):
728 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400729 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400730 self.assertTrue(getattr(zoo, 'animals'))
731 request = zoo.global_().print_().assert_(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800732 parsed = urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400733 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400734
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500735 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500736 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400737 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500738 self.assertTrue(getattr(zoo, 'query'))
739 request = zoo.query(q="foo")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800740 parsed = urlparse(request.uri)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500741 q = parse_qs(parsed[4])
742 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400743
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400744 def test_simple_media_uploads(self):
745 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400746 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400747 doc = getattr(zoo.animals().insert, '__doc__')
748 self.assertTrue('media_body' in doc)
749
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400750 def test_simple_media_upload_no_max_size_provided(self):
751 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400752 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400753 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
754 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800755 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400756
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400757 def test_simple_media_raise_correct_exceptions(self):
758 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400759 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400760
761 try:
762 zoo.animals().insert(media_body=datafile('smiley.png'))
763 self.fail("should throw exception if media is too large.")
764 except MediaUploadSizeError:
765 pass
766
767 try:
768 zoo.animals().insert(media_body=datafile('small.jpg'))
769 self.fail("should throw exception if mimetype is unacceptable.")
770 except UnacceptableMimeTypeError:
771 pass
772
773 def test_simple_media_good_upload(self):
774 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400775 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400776
777 request = zoo.animals().insert(media_body=datafile('small.png'))
778 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800779 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500780 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400781 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500782 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400783
Brian J. Watson38051ac2016-10-25 07:53:08 -0700784 def test_simple_media_unknown_mimetype(self):
785 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
786 zoo = build('zoo', 'v1', http=self.http)
787
788 try:
789 zoo.animals().insert(media_body=datafile('small-png'))
790 self.fail("should throw exception if mimetype is unknown.")
791 except UnknownFileType:
792 pass
793
794 request = zoo.animals().insert(media_body=datafile('small-png'),
795 media_mime_type='image/png')
796 self.assertEquals('image/png', request.headers['content-type'])
797 self.assertEquals(b'PNG', request.body[1:4])
798 assertUrisEqual(self,
799 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
800 request.uri)
801
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400802 def test_multipart_media_raise_correct_exceptions(self):
803 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400804 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400805
806 try:
807 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
808 self.fail("should throw exception if media is too large.")
809 except MediaUploadSizeError:
810 pass
811
812 try:
813 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
814 self.fail("should throw exception if mimetype is unacceptable.")
815 except UnacceptableMimeTypeError:
816 pass
817
818 def test_multipart_media_good_upload(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 Gregoriofdf7c802011-06-30 12:33:38 -0400821
822 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400823 self.assertTrue(request.headers['content-type'].startswith(
824 'multipart/related'))
Phil Ruffwind26178fc2015-10-13 19:00:33 -0400825 with open(datafile('small.png'), 'rb') as f:
826 contents = f.read()
827 boundary = re.match(b'--=+([^=]+)', request.body).group(1)
828 self.assertEqual(
829 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
830 b'--===============' + boundary + b'==\n' +
831 b'Content-Type: application/json\n' +
832 b'MIME-Version: 1.0\n\n' +
833 b'{"data": {}}\n' +
834 b'--===============' + boundary + b'==\n' +
835 b'Content-Type: image/png\n' +
836 b'MIME-Version: 1.0\n' +
837 b'Content-Transfer-Encoding: binary\n\n' +
838 contents +
839 b'\n--===============' + boundary + b'==--')
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500840 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400841 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500842 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400843
844 def test_media_capable_method_without_media(self):
845 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400846 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400847
848 request = zoo.animals().insert(body={})
849 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400850
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500851 def test_resumable_multipart_media_good_upload(self):
852 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400853 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500854
855 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
856 request = zoo.animals().insert(media_body=media_upload, body={})
857 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500858 'application/json'))
859 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500860 self.assertEquals(media_upload, request.resumable)
861
862 self.assertEquals('image/png', request.resumable.mimetype())
863
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500864 self.assertNotEquals(request.body, None)
865 self.assertEquals(request.resumable_uri, None)
866
867 http = HttpMockSequence([
868 ({'status': '200',
869 'location': 'http://upload.example.com'}, ''),
870 ({'status': '308',
Matt Carroll94a53942016-12-20 13:56:43 -0800871 'location': 'http://upload.example.com/2'}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500872 ({'status': '308',
873 'location': 'http://upload.example.com/3',
Matt Carroll94a53942016-12-20 13:56:43 -0800874 'range': '0-12'}, ''),
875 ({'status': '308',
876 'location': 'http://upload.example.com/4',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500877 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500878 ({'status': '200'}, '{"foo": "bar"}'),
879 ])
880
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400881 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500882 self.assertEquals(None, body)
883 self.assertTrue(isinstance(status, MediaUploadProgress))
Matt Carroll94a53942016-12-20 13:56:43 -0800884 self.assertEquals(0, status.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500885
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500886 # Two requests should have been made and the resumable_uri should have been
887 # updated for each one.
888 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
Matt Carroll94a53942016-12-20 13:56:43 -0800889 self.assertEquals(media_upload, request.resumable)
890 self.assertEquals(0, request.resumable_progress)
891
892 # This next chuck call should upload the first chunk
893 status, body = request.next_chunk(http=http)
894 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500895 self.assertEquals(media_upload, request.resumable)
896 self.assertEquals(13, request.resumable_progress)
897
Matt Carroll94a53942016-12-20 13:56:43 -0800898 # This call will upload the next chunk
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400899 status, body = request.next_chunk(http=http)
Matt Carroll94a53942016-12-20 13:56:43 -0800900 self.assertEquals(request.resumable_uri, 'http://upload.example.com/4')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500901 self.assertEquals(media_upload.size()-1, request.resumable_progress)
902 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500903
904 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400905 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500906 self.assertEquals(body, {"foo": "bar"})
907 self.assertEquals(status, None)
908
909
910 def test_resumable_media_good_upload(self):
911 """Not a multipart upload."""
912 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400913 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500914
915 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
916 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500917 self.assertEquals(media_upload, request.resumable)
918
919 self.assertEquals('image/png', request.resumable.mimetype())
920
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500921 self.assertEquals(request.body, None)
922 self.assertEquals(request.resumable_uri, None)
923
924 http = HttpMockSequence([
925 ({'status': '200',
926 'location': 'http://upload.example.com'}, ''),
927 ({'status': '308',
928 'location': 'http://upload.example.com/2',
929 'range': '0-12'}, ''),
930 ({'status': '308',
931 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500932 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500933 ({'status': '200'}, '{"foo": "bar"}'),
934 ])
935
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400936 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500937 self.assertEquals(None, body)
938 self.assertTrue(isinstance(status, MediaUploadProgress))
939 self.assertEquals(13, status.resumable_progress)
940
941 # Two requests should have been made and the resumable_uri should have been
942 # updated for each one.
943 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
944
945 self.assertEquals(media_upload, request.resumable)
946 self.assertEquals(13, request.resumable_progress)
947
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400948 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500949 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500950 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500951 self.assertEquals(request.body, None)
952
953 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400954 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500955 self.assertEquals(body, {"foo": "bar"})
956 self.assertEquals(status, None)
957
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500958 def test_resumable_media_good_upload_from_execute(self):
959 """Not a multipart upload."""
960 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400961 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500962
963 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
964 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500965 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400966 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500967 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500968
969 http = HttpMockSequence([
970 ({'status': '200',
971 'location': 'http://upload.example.com'}, ''),
972 ({'status': '308',
973 'location': 'http://upload.example.com/2',
974 'range': '0-12'}, ''),
975 ({'status': '308',
976 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500977 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500978 ({'status': '200'}, '{"foo": "bar"}'),
979 ])
980
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400981 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500982 self.assertEquals(body, {"foo": "bar"})
983
984 def test_resumable_media_fail_unknown_response_code_first_request(self):
985 """Not a multipart upload."""
986 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400987 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500988
989 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
990 request = zoo.animals().insert(media_body=media_upload, body=None)
991
992 http = HttpMockSequence([
993 ({'status': '400',
994 'location': 'http://upload.example.com'}, ''),
995 ])
996
Joe Gregoriobaf04802013-03-01 12:27:06 -0500997 try:
998 request.execute(http=http)
999 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +09001000 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -05001001 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001002
1003 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
1004 """Not a multipart upload."""
1005 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001006 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001007
1008 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
1009 request = zoo.animals().insert(media_body=media_upload, body=None)
1010
1011 http = HttpMockSequence([
1012 ({'status': '200',
1013 'location': 'http://upload.example.com'}, ''),
1014 ({'status': '400'}, ''),
1015 ])
1016
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001017 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001018 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001019
Joe Gregorio910b9b12012-06-12 09:36:30 -04001020 http = HttpMockSequence([
1021 ({'status': '308',
1022 'range': '0-5'}, ''),
1023 ({'status': '308',
1024 'range': '0-6'}, ''),
1025 ])
1026
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001027 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001028 self.assertEquals(status.resumable_progress, 7,
1029 'Should have first checked length and then tried to PUT more.')
1030 self.assertFalse(request._in_error_state)
1031
1032 # Put it back in an error state.
1033 http = HttpMockSequence([
1034 ({'status': '400'}, ''),
1035 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001036 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001037 self.assertTrue(request._in_error_state)
1038
1039 # Pretend the last request that 400'd actually succeeded.
1040 http = HttpMockSequence([
1041 ({'status': '200'}, '{"foo": "bar"}'),
1042 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001043 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001044 self.assertEqual(body, {'foo': 'bar'})
1045
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001046 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1047 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1048 zoo = build('zoo', 'v1', http=self.http)
1049
Pat Ferateed9affd2015-03-03 16:03:15 -08001050 # Set up a seekable stream and try to upload in single chunk.
Pat Ferate2b140222015-03-03 18:05:11 -08001051 fd = BytesIO(b'01234"56789"')
Pat Ferateed9affd2015-03-03 16:03:15 -08001052 media_upload = MediaIoBaseUpload(
1053 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001054
Pat Ferateed9affd2015-03-03 16:03:15 -08001055 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001056
Pat Ferateed9affd2015-03-03 16:03:15 -08001057 # The single chunk fails, restart at the right point.
1058 http = HttpMockSequence([
1059 ({'status': '200',
1060 'location': 'http://upload.example.com'}, ''),
1061 ({'status': '308',
1062 'location': 'http://upload.example.com/2',
1063 'range': '0-4'}, ''),
1064 ({'status': '200'}, 'echo_request_body'),
1065 ])
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001066
Pat Ferateed9affd2015-03-03 16:03:15 -08001067 body = request.execute(http=http)
1068 self.assertEqual('56789', body)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001069
1070 def test_media_io_base_stream_chunksize_resume(self):
1071 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1072 zoo = build('zoo', 'v1', http=self.http)
1073
Pat Ferateed9affd2015-03-03 16:03:15 -08001074 # Set up a seekable stream and try to upload in chunks.
Pat Ferate2b140222015-03-03 18:05:11 -08001075 fd = BytesIO(b'0123456789')
Pat Ferateed9affd2015-03-03 16:03:15 -08001076 media_upload = MediaIoBaseUpload(
1077 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
1078
1079 request = zoo.animals().insert(media_body=media_upload, body=None)
1080
1081 # The single chunk fails, pull the content sent out of the exception.
1082 http = HttpMockSequence([
1083 ({'status': '200',
1084 'location': 'http://upload.example.com'}, ''),
1085 ({'status': '400'}, 'echo_request_body'),
1086 ])
1087
Joe Gregorio5c120db2012-08-23 09:13:55 -04001088 try:
Pat Ferateed9affd2015-03-03 16:03:15 -08001089 body = request.execute(http=http)
1090 except HttpError as e:
Pat Ferate2b140222015-03-03 18:05:11 -08001091 self.assertEqual(b'01234', e.content)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001092
Joe Gregorio910b9b12012-06-12 09:36:30 -04001093 def test_resumable_media_handle_uploads_of_unknown_size(self):
1094 http = HttpMockSequence([
1095 ({'status': '200',
1096 'location': 'http://upload.example.com'}, ''),
1097 ({'status': '200'}, 'echo_request_headers_as_json'),
1098 ])
1099
1100 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001101 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001102
Joe Gregorio910b9b12012-06-12 09:36:30 -04001103 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001104 class IoBaseUnknownLength(MediaUpload):
1105 def chunksize(self):
1106 return 10
1107
1108 def mimetype(self):
1109 return 'image/png'
1110
1111 def size(self):
1112 return None
1113
1114 def resumable(self):
1115 return True
1116
1117 def getbytes(self, begin, length):
1118 return '0123456789'
1119
1120 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -04001121
1122 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001123 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001124 self.assertEqual(body, {
1125 'Content-Range': 'bytes 0-9/*',
1126 'Content-Length': '10',
1127 })
Joe Gregorio44454e42012-06-15 08:38:53 -04001128
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001129 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1130 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1131 zoo = build('zoo', 'v1', http=self.http)
1132
1133 class IoBaseHasStream(MediaUpload):
1134 def chunksize(self):
1135 return 10
1136
1137 def mimetype(self):
1138 return 'image/png'
1139
1140 def size(self):
1141 return None
1142
1143 def resumable(self):
1144 return True
1145
1146 def getbytes(self, begin, length):
1147 return '0123456789'
1148
1149 def has_stream(self):
1150 return True
1151
1152 def stream(self):
1153 raise NotImplementedError()
1154
1155 upload = IoBaseHasStream()
1156
1157 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001158
1159 sys.version_info = (2, 6, 5, 'final', 0)
1160
1161 request = zoo.animals().insert(media_body=upload, body=None)
1162
1163 # This should raise an exception because stream() will be called.
1164 http = HttpMockSequence([
1165 ({'status': '200',
1166 'location': 'http://upload.example.com'}, ''),
1167 ({'status': '200'}, 'echo_request_headers_as_json'),
1168 ])
1169
1170 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
1171
1172 sys.version_info = orig_version
1173
Joe Gregorio44454e42012-06-15 08:38:53 -04001174 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1175 http = HttpMockSequence([
1176 ({'status': '200',
1177 'location': 'http://upload.example.com'}, ''),
1178 ({'status': '200'}, 'echo_request_headers_as_json'),
1179 ])
1180
1181 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001182 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001183
Pat Ferate2b140222015-03-03 18:05:11 -08001184 fd = BytesIO(b'data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001185
1186 # Create an upload that doesn't know the full size of the media.
1187 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001188 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001189
1190 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001191 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001192 self.assertEqual(body, {
1193 'Content-Range': 'bytes 0-13/14',
1194 'Content-Length': '14',
1195 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001196
1197 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1198 http = HttpMockSequence([
1199 ({'status': '200',
1200 'location': 'http://upload.example.com'}, ''),
1201 ({'status': '400'}, ''),
1202 ])
1203
1204 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001205 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001206
1207 # Create an upload that doesn't know the full size of the media.
Pat Ferate2b140222015-03-03 18:05:11 -08001208 fd = BytesIO(b'data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001209
1210 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001211 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001212
1213 request = zoo.animals().insert(media_body=upload, body=None)
1214
1215 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001216 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001217
1218 http = HttpMockSequence([
1219 ({'status': '400',
1220 'range': '0-5'}, 'echo_request_headers_as_json'),
1221 ])
1222 try:
1223 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001224 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001225 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001226 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001227 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001228 'content-length': '0'
1229 }
INADA Naoki09157612015-03-25 01:51:03 +09001230 self.assertEqual(expected, json.loads(e.content.decode('utf-8')),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001231 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001232
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001233 def test_pickle(self):
1234 sorted_resource_keys = ['_baseUrl',
1235 '_developerKey',
1236 '_dynamic_attrs',
1237 '_http',
1238 '_model',
1239 '_requestBuilder',
1240 '_resourceDesc',
1241 '_rootDesc',
1242 '_schema',
1243 'animals',
1244 'global_',
1245 'load',
1246 'loadNoTemplate',
1247 'my',
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001248 'new_batch_http_request',
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001249 'query',
1250 'scopedAnimals']
1251
1252 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1253 zoo = build('zoo', 'v1', http=http)
1254 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1255
1256 pickled_zoo = pickle.dumps(zoo)
1257 new_zoo = pickle.loads(pickled_zoo)
1258 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1259 self.assertTrue(hasattr(new_zoo, 'animals'))
1260 self.assertTrue(callable(new_zoo.animals))
1261 self.assertTrue(hasattr(new_zoo, 'global_'))
1262 self.assertTrue(callable(new_zoo.global_))
1263 self.assertTrue(hasattr(new_zoo, 'load'))
1264 self.assertTrue(callable(new_zoo.load))
1265 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1266 self.assertTrue(callable(new_zoo.loadNoTemplate))
1267 self.assertTrue(hasattr(new_zoo, 'my'))
1268 self.assertTrue(callable(new_zoo.my))
1269 self.assertTrue(hasattr(new_zoo, 'query'))
1270 self.assertTrue(callable(new_zoo.query))
1271 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1272 self.assertTrue(callable(new_zoo.scopedAnimals))
1273
Joe Gregorio003b6e42013-02-13 15:42:19 -05001274 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001275 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1276 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1277 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1278 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1279 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1280 # _http, _model and _schema won't be equal since we will get new
1281 # instances upon un-pickling
1282
1283 def _dummy_zoo_request(self):
1284 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1285 zoo_contents = fh.read()
1286
1287 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1288 {'api': 'zoo', 'apiVersion': 'v1'})
1289 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001290 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1291 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001292
1293 http = httplib2.Http()
1294 original_request = http.request
1295 def wrapped_request(uri, method='GET', *args, **kwargs):
1296 if uri == zoo_uri:
1297 return httplib2.Response({'status': '200'}), zoo_contents
1298 return original_request(uri, method=method, *args, **kwargs)
1299 http.request = wrapped_request
1300 return http
1301
1302 def _dummy_token(self):
1303 access_token = 'foo'
1304 client_id = 'some_client_id'
1305 client_secret = 'cOuDdkfjxxnv+'
1306 refresh_token = '1/0/a.df219fjls0'
1307 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001308 user_agent = 'refresh_checker/1.0'
1309 return OAuth2Credentials(
1310 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001311 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001312 user_agent)
1313
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001314 def test_pickle_with_credentials(self):
1315 credentials = self._dummy_token()
1316 http = self._dummy_zoo_request()
1317 http = credentials.authorize(http)
1318 self.assertTrue(hasattr(http.request, 'credentials'))
1319
1320 zoo = build('zoo', 'v1', http=http)
1321 pickled_zoo = pickle.dumps(zoo)
1322 new_zoo = pickle.loads(pickled_zoo)
1323 self.assertEqual(sorted(zoo.__dict__.keys()),
1324 sorted(new_zoo.__dict__.keys()))
1325 new_http = new_zoo._http
1326 self.assertFalse(hasattr(new_http.request, 'credentials'))
1327
Joe Gregorio708388c2012-06-15 13:43:04 -04001328
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001329class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001330
Joe Gregorio3c676f92011-07-25 10:38:14 -04001331 def test_next_successful_none_on_no_next_page_token(self):
1332 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001333 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001334 request = tasks.tasklists().list()
1335 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1336
Son Dinh2a9a2132015-07-23 16:30:56 +00001337 def test_next_successful_none_on_empty_page_token(self):
1338 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
1339 tasks = build('tasks', 'v1', http=self.http)
1340 request = tasks.tasklists().list()
1341 next_request = tasks.tasklists().list_next(
1342 request, {'nextPageToken': ''})
1343 self.assertEqual(None, next_request)
1344
Joe Gregorio3c676f92011-07-25 10:38:14 -04001345 def test_next_successful_with_next_page_token(self):
1346 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001347 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001348 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001349 next_request = tasks.tasklists().list_next(
1350 request, {'nextPageToken': '123abc'})
Pat Ferated5b61bd2015-03-03 16:04:11 -08001351 parsed = list(urlparse(next_request.uri))
Joe Gregorio3c676f92011-07-25 10:38:14 -04001352 q = parse_qs(parsed[4])
1353 self.assertEqual(q['pageToken'][0], '123abc')
1354
Joe Gregorio555f33c2011-08-19 14:56:07 -04001355 def test_next_with_method_with_no_properties(self):
1356 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001357 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001358 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001359
Joe Gregorioa98733f2011-09-16 10:12:28 -04001360
Joe Gregorio708388c2012-06-15 13:43:04 -04001361class MediaGet(unittest.TestCase):
1362
1363 def test_get_media(self):
1364 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001365 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001366 request = zoo.animals().get_media(name='Lion')
1367
Pat Ferated5b61bd2015-03-03 16:04:11 -08001368 parsed = urlparse(request.uri)
Joe Gregorio708388c2012-06-15 13:43:04 -04001369 q = parse_qs(parsed[4])
1370 self.assertEqual(q['alt'], ['media'])
1371 self.assertEqual(request.headers['accept'], '*/*')
1372
1373 http = HttpMockSequence([
1374 ({'status': '200'}, 'standing in for media'),
1375 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001376 response = request.execute(http=http)
INADA Naoki09157612015-03-25 01:51:03 +09001377 self.assertEqual(b'standing in for media', response)
Joe Gregorio708388c2012-06-15 13:43:04 -04001378
1379
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001380if __name__ == '__main__':
1381 unittest.main()