blob: dea563179a82119cc4d051c1444abf8c1d38ae25 [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
53from googleapiclient.discovery import ResourceMethodParameters
54from googleapiclient.discovery import STACK_QUERY_PARAMETERS
55from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
Takashi Matsuo30125122015-08-19 11:42:32 -070056from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE
57from googleapiclient.discovery_cache.base import Cache
John Asmuth864311d2014-04-24 15:46:08 -040058from googleapiclient.errors import HttpError
59from googleapiclient.errors import InvalidJsonError
60from googleapiclient.errors import MediaUploadSizeError
61from googleapiclient.errors import ResumableUploadError
62from googleapiclient.errors import UnacceptableMimeTypeError
Takashi Matsuo3772f9d2015-09-04 12:25:55 -070063from googleapiclient.errors import UnknownApiNameOrVersion
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -040064from googleapiclient.http import BatchHttpRequest
John Asmuth864311d2014-04-24 15:46:08 -040065from googleapiclient.http import HttpMock
66from googleapiclient.http import HttpMockSequence
67from googleapiclient.http import MediaFileUpload
68from googleapiclient.http import MediaIoBaseUpload
69from googleapiclient.http import MediaUpload
70from googleapiclient.http import MediaUploadProgress
71from googleapiclient.http import tunnel_patch
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080072from oauth2client import GOOGLE_TOKEN_URI
Joe Gregorio79daca02013-03-29 16:25:52 -040073from oauth2client import util
Joe Gregoriodc106fc2012-11-20 14:30:14 -050074from oauth2client.client import OAuth2Credentials
Joe Gregorio79daca02013-03-29 16:25:52 -040075
Joe Gregoriodc106fc2012-11-20 14:30:14 -050076import uritemplate
77
Joe Gregoriocb8103d2011-02-11 23:20:52 -050078
79DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
80
Joe Gregorio79daca02013-03-29 16:25:52 -040081util.positional_parameters_enforcement = util.POSITIONAL_EXCEPTION
Joe Gregorio32f048f2012-08-27 16:31:27 -040082
Joe Gregorioa98733f2011-09-16 10:12:28 -040083
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050084def assertUrisEqual(testcase, expected, actual):
85 """Test that URIs are the same, up to reordering of query parameters."""
Pat Ferated5b61bd2015-03-03 16:04:11 -080086 expected = urlparse(expected)
87 actual = urlparse(actual)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050088 testcase.assertEqual(expected.scheme, actual.scheme)
89 testcase.assertEqual(expected.netloc, actual.netloc)
90 testcase.assertEqual(expected.path, actual.path)
91 testcase.assertEqual(expected.params, actual.params)
92 testcase.assertEqual(expected.fragment, actual.fragment)
93 expected_query = parse_qs(expected.query)
94 actual_query = parse_qs(actual.query)
INADA Naokid898a372015-03-04 03:52:46 +090095 for name in list(expected_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050096 testcase.assertEqual(expected_query[name], actual_query[name])
INADA Naokid898a372015-03-04 03:52:46 +090097 for name in list(actual_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050098 testcase.assertEqual(expected_query[name], actual_query[name])
99
100
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500101def datafile(filename):
102 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400103
104
Joe Gregorio504a17f2012-12-07 14:14:26 -0500105class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800106
Joe Gregorio504a17f2012-12-07 14:14:26 -0500107 def test_retries(self):
John Asmuth864311d2014-04-24 15:46:08 -0400108 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
Joe Gregorio504a17f2012-12-07 14:14:26 -0500109 self.assertEqual(1, httplib2.RETRIES)
110
111
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400112class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800113
114 def setUp(self):
115 with open(datafile('zoo.json'), 'r') as fh:
Craig Citro6ae34d72014-08-18 23:10:09 -0700116 self.zoo_root_desc = json.loads(fh.read())
Daniel Hermesc2113242013-02-27 10:16:13 -0800117 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800118 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
119 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Daniel Hermesc2113242013-02-27 10:16:13 -0800120
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400121 def test_key2param(self):
122 self.assertEqual('max_results', key2param('max-results'))
123 self.assertEqual('x007_bond', key2param('007-bond'))
124
Daniel Hermesc2113242013-02-27 10:16:13 -0800125 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
126 self.assertEqual(method_desc['httpMethod'], http_method)
127
128 method_desc_copy = copy.deepcopy(method_desc)
129 self.assertEqual(method_desc, method_desc_copy)
130
131 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
132
133 self.assertNotEqual(method_desc, method_desc_copy)
134
135 for param_name in STACK_QUERY_PARAMETERS:
136 self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
137 parameters[param_name])
138
INADA Naokid898a372015-03-04 03:52:46 +0900139 for param_name, value in six.iteritems(root_desc.get('parameters', {})):
Daniel Hermesc2113242013-02-27 10:16:13 -0800140 self.assertEqual(value, parameters[param_name])
141
142 return parameters
143
144 def test_fix_up_parameters_get(self):
145 parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
146 'GET', self.zoo_root_desc)
147 # Since http_method is 'GET'
INADA Naoki0bceb332014-08-20 15:27:52 +0900148 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800149
150 def test_fix_up_parameters_insert(self):
151 parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
152 'POST', self.zoo_root_desc)
153 body = {
154 'description': 'The request body.',
155 'type': 'object',
156 'required': True,
157 '$ref': 'Animal',
158 }
159 self.assertEqual(parameters['body'], body)
160
161 def test_fix_up_parameters_check_body(self):
162 dummy_root_desc = {}
163 no_payload_http_method = 'DELETE'
164 with_payload_http_method = 'PUT'
165
166 invalid_method_desc = {'response': 'Who cares'}
167 valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
168
169 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
170 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900171 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800172
173 parameters = _fix_up_parameters(valid_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(invalid_method_desc, dummy_root_desc,
178 with_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(valid_method_desc, dummy_root_desc,
182 with_payload_http_method)
183 body = {
184 'description': 'The request body.',
185 'type': 'object',
186 'required': True,
187 'key1': 'value1',
188 'key2': 'value2',
189 }
190 self.assertEqual(parameters['body'], body)
191
192 def _base_fix_up_method_description_test(
193 self, method_desc, initial_parameters, final_parameters,
194 final_accept, final_max_size, final_media_path_url):
195 fake_root_desc = {'rootUrl': 'http://root/',
196 'servicePath': 'fake/'}
197 fake_path_url = 'fake-path/'
198
199 accept, max_size, media_path_url = _fix_up_media_upload(
200 method_desc, fake_root_desc, fake_path_url, initial_parameters)
201 self.assertEqual(accept, final_accept)
202 self.assertEqual(max_size, final_max_size)
203 self.assertEqual(media_path_url, final_media_path_url)
204 self.assertEqual(initial_parameters, final_parameters)
205
206 def test_fix_up_media_upload_no_initial_invalid(self):
207 invalid_method_desc = {'response': 'Who cares'}
208 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
209 [], 0, None)
210
211 def test_fix_up_media_upload_no_initial_valid_minimal(self):
212 valid_method_desc = {'mediaUpload': {'accept': []}}
213 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
214 self._base_fix_up_method_description_test(
215 valid_method_desc, {}, final_parameters, [], 0,
216 'http://root/upload/fake/fake-path/')
217
218 def test_fix_up_media_upload_no_initial_valid_full(self):
219 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
220 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
221 ten_gb = 10 * 2**30
222 self._base_fix_up_method_description_test(
223 valid_method_desc, {}, final_parameters, ['*/*'],
224 ten_gb, 'http://root/upload/fake/fake-path/')
225
226 def test_fix_up_media_upload_with_initial_invalid(self):
227 invalid_method_desc = {'response': 'Who cares'}
228 initial_parameters = {'body': {}}
229 self._base_fix_up_method_description_test(
230 invalid_method_desc, initial_parameters,
231 initial_parameters, [], 0, None)
232
233 def test_fix_up_media_upload_with_initial_valid_minimal(self):
234 valid_method_desc = {'mediaUpload': {'accept': []}}
235 initial_parameters = {'body': {}}
236 final_parameters = {'body': {'required': False},
237 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
238 self._base_fix_up_method_description_test(
239 valid_method_desc, initial_parameters, final_parameters, [], 0,
240 'http://root/upload/fake/fake-path/')
241
242 def test_fix_up_media_upload_with_initial_valid_full(self):
243 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
244 initial_parameters = {'body': {}}
245 final_parameters = {'body': {'required': False},
246 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
247 ten_gb = 10 * 2**30
248 self._base_fix_up_method_description_test(
249 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
250 ten_gb, 'http://root/upload/fake/fake-path/')
251
252 def test_fix_up_method_description_get(self):
253 result = _fix_up_method_description(self.zoo_get_method_desc,
254 self.zoo_root_desc)
255 path_url = 'query'
256 http_method = 'GET'
257 method_id = 'bigquery.query'
258 accept = []
INADA Naoki0bceb332014-08-20 15:27:52 +0900259 max_size = 0
Daniel Hermesc2113242013-02-27 10:16:13 -0800260 media_path_url = None
261 self.assertEqual(result, (path_url, http_method, method_id, accept,
262 max_size, media_path_url))
263
264 def test_fix_up_method_description_insert(self):
265 result = _fix_up_method_description(self.zoo_insert_method_desc,
266 self.zoo_root_desc)
267 path_url = 'animals'
268 http_method = 'POST'
269 method_id = 'zoo.animals.insert'
270 accept = ['image/png']
INADA Naoki0bceb332014-08-20 15:27:52 +0900271 max_size = 1024
Daniel Hermesc2113242013-02-27 10:16:13 -0800272 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
273 self.assertEqual(result, (path_url, http_method, method_id, accept,
274 max_size, media_path_url))
275
Craig Citro7ee535d2015-02-23 10:11:14 -0800276 def test_urljoin(self):
277 # We want to exhaustively test various URL combinations.
278 simple_bases = ['https://www.googleapis.com', 'https://www.googleapis.com/']
279 long_urls = ['foo/v1/bar:custom?alt=json', '/foo/v1/bar:custom?alt=json']
280
281 long_bases = [
282 'https://www.googleapis.com/foo/v1',
283 'https://www.googleapis.com/foo/v1/',
284 ]
285 simple_urls = ['bar:custom?alt=json', '/bar:custom?alt=json']
286
287 final_url = 'https://www.googleapis.com/foo/v1/bar:custom?alt=json'
288 for base, url in itertools.product(simple_bases, long_urls):
289 self.assertEqual(final_url, _urljoin(base, url))
290 for base, url in itertools.product(long_bases, simple_urls):
291 self.assertEqual(final_url, _urljoin(base, url))
292
293
Daniel Hermes954e1242013-02-28 09:28:37 -0800294 def test_ResourceMethodParameters_zoo_get(self):
295 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
296
297 param_types = {'a': 'any',
298 'b': 'boolean',
299 'e': 'string',
300 'er': 'string',
301 'i': 'integer',
302 'n': 'number',
303 'o': 'object',
304 'q': 'string',
305 'rr': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900306 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800307 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
308 self.assertEqual(parameters.required_params, [])
309 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
310 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
311 self.assertEqual(sorted(parameters.query_params),
312 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
313 self.assertEqual(parameters.path_params, set())
314 self.assertEqual(parameters.param_types, param_types)
315 enum_params = {'e': ['foo', 'bar'],
316 'er': ['one', 'two', 'three']}
317 self.assertEqual(parameters.enum_params, enum_params)
318
319 def test_ResourceMethodParameters_zoo_animals_patch(self):
320 method_desc = self.zoo_animals_resource['methods']['patch']
321 parameters = ResourceMethodParameters(method_desc)
322
323 param_types = {'name': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900324 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800325 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
326 self.assertEqual(parameters.required_params, ['name'])
327 self.assertEqual(parameters.repeated_params, [])
328 self.assertEqual(parameters.pattern_params, {})
329 self.assertEqual(parameters.query_params, [])
330 self.assertEqual(parameters.path_params, set(['name']))
331 self.assertEqual(parameters.param_types, param_types)
332 self.assertEqual(parameters.enum_params, {})
333
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400334
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500335class DiscoveryErrors(unittest.TestCase):
336
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400337 def test_tests_should_be_run_with_strict_positional_enforcement(self):
338 try:
339 plus = build('plus', 'v1', None)
340 self.fail("should have raised a TypeError exception over missing http=.")
341 except TypeError:
342 pass
343
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500344 def test_failed_to_parse_discovery_json(self):
345 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
346 try:
Takashi Matsuo30125122015-08-19 11:42:32 -0700347 plus = build('plus', 'v1', http=self.http, cache_discovery=False)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500348 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500349 except InvalidJsonError:
350 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500351
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700352 def test_unknown_api_name_or_version(self):
353 http = HttpMockSequence([
354 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Ethan Bao12b7cd32016-03-14 14:25:10 -0700355 ({'status': '404'}, open(datafile('zoo.json'), 'rb').read()),
Takashi Matsuo3772f9d2015-09-04 12:25:55 -0700356 ])
357 with self.assertRaises(UnknownApiNameOrVersion):
358 plus = build('plus', 'v1', http=http, cache_discovery=False)
359
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500360
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100361class DiscoveryFromDocument(unittest.TestCase):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400362
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100363 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400364 discovery = open(datafile('plus.json')).read()
Joe Gregorio7b70f432011-11-09 10:18:51 -0500365 plus = build_from_document(discovery, base="https://www.googleapis.com/")
366 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500367 self.assertTrue(hasattr(plus, 'activities'))
368
369 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400370 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700371 discovery = json.loads(discovery)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500372 plus = build_from_document(discovery, base="https://www.googleapis.com/")
373 self.assertTrue(plus is not None)
374 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400375
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100376 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400377 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400378
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100379 base = "https://www.example.com/"
Joe Gregorio7b70f432011-11-09 10:18:51 -0500380 plus = build_from_document(discovery, base=base)
Joe Gregorioa2838152012-07-16 11:52:17 -0400381 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100382
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700383 def test_building_with_optional_http(self):
384 discovery = open(datafile('plus.json')).read()
385 plus = build_from_document(discovery, base="https://www.googleapis.com/")
386 self.assertTrue(isinstance(plus._http, httplib2.Http))
387
388 def test_building_with_explicit_http(self):
389 http = HttpMock()
390 discovery = open(datafile('plus.json')).read()
391 plus = build_from_document(
392 discovery, base="https://www.googleapis.com/", http=http)
393 self.assertEquals(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100394
Joe Gregorioa98733f2011-09-16 10:12:28 -0400395class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400396 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700397 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400398
Joe Gregorio583d9e42011-09-16 15:54:15 -0400399 def tearDown(self):
400 os.environ = self.old_environ
401
402 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400403 # build() will raise an HttpError on a 400, use this to pick the request uri
404 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400405 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400406 try:
407 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400408 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400409 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400410 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400411 discoveryServiceUrl='http://example.com')
412 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900413 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400414 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400415
Joe Gregorio583d9e42011-09-16 15:54:15 -0400416 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400417 # build() will raise an HttpError on a 400, use this to pick the request uri
418 # out of the raised exception.
419 try:
420 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400421 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400422 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400423 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400424 discoveryServiceUrl='http://example.com')
425 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900426 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400427 self.assertEqual(e.uri, 'http://example.com')
428
Ethan Bao12b7cd32016-03-14 14:25:10 -0700429 def test_discovery_loading_from_v2_discovery_uri(self):
430 http = HttpMockSequence([
431 ({'status': '404'}, 'Not found'),
432 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
433 ])
434 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
435 self.assertTrue(hasattr(zoo, 'animals'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400436
Takashi Matsuo30125122015-08-19 11:42:32 -0700437class DiscoveryFromAppEngineCache(unittest.TestCase):
438 def test_appengine_memcache(self):
439 # Hack module import
440 self.orig_import = __import__
441 self.mocked_api = mock.MagicMock()
442
443 def import_mock(name, *args):
444 if name == 'google.appengine.api':
445 return self.mocked_api
446 return self.orig_import(name, *args)
447
448 import_fullname = '__builtin__.__import__'
449 if sys.version_info[0] >= 3:
450 import_fullname = 'builtins.__import__'
451
452 with mock.patch(import_fullname, side_effect=import_mock):
453 namespace = 'google-api-client'
454 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
455
456 self.mocked_api.memcache.get.return_value = None
457
458 plus = build('plus', 'v1', http=self.http)
459
460 # memcache.get is called once
461 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
462 self.mocked_api.memcache.get.assert_called_once_with(url,
463 namespace=namespace)
464
465 # memcache.set is called once
466 with open(datafile('plus.json')) as f:
467 content = f.read()
468 self.mocked_api.memcache.set.assert_called_once_with(
469 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace)
470
471 # Returns the cached content this time.
472 self.mocked_api.memcache.get.return_value = content
473
474 # Make sure the contents are returned from the cache.
475 # (Otherwise it should through an error)
476 self.http = HttpMock(None, {'status': '200'})
477
478 plus = build('plus', 'v1', http=self.http)
479
480 # memcache.get is called twice
481 self.mocked_api.memcache.get.assert_has_calls(
482 [mock.call(url, namespace=namespace),
483 mock.call(url, namespace=namespace)])
484
485 # memcahce.set is called just once
486 self.mocked_api.memcache.set.assert_called_once_with(
487 url, content, time=DISCOVERY_DOC_MAX_AGE,namespace=namespace)
488
489
490class DictCache(Cache):
491 def __init__(self):
492 self.d = {}
493 def get(self, url):
494 return self.d.get(url, None)
495 def set(self, url, content):
496 self.d[url] = content
497 def contains(self, url):
498 return url in self.d
499
500
501class DiscoveryFromFileCache(unittest.TestCase):
502 def test_file_based_cache(self):
503 cache = mock.Mock(wraps=DictCache())
504 with mock.patch('googleapiclient.discovery_cache.file_cache.cache',
505 new=cache):
506 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
507
508 plus = build('plus', 'v1', http=self.http)
509
510 # cache.get is called once
511 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
512 cache.get.assert_called_once_with(url)
513
514 # cache.set is called once
515 with open(datafile('plus.json')) as f:
516 content = f.read()
517 cache.set.assert_called_once_with(url, content)
518
519 # Make sure there is a cache entry for the plus v1 discovery doc.
520 self.assertTrue(cache.contains(url))
521
522 # Make sure the contents are returned from the cache.
523 # (Otherwise it should through an error)
524 self.http = HttpMock(None, {'status': '200'})
525
526 plus = build('plus', 'v1', http=self.http)
527
528 # cache.get is called twice
529 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
530
531 # cahce.set is called just once
532 cache.set.assert_called_once_with(url, content)
533
534
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400535class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400536
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400537 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500538 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400539 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400540
541 # Missing required parameters
542 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500543 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400544 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900545 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400546 self.assertTrue('Missing' in str(e))
547
Joe Gregorio2467afa2012-06-20 12:21:25 -0400548 # Missing required parameters even if supplied as None.
549 try:
550 plus.activities().list(collection=None, userId=None)
551 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900552 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400553 self.assertTrue('Missing' in str(e))
554
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400555 # Parameter doesn't match regex
556 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500557 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400558 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900559 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500560 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400561
562 # Unexpected parameter
563 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500564 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400565 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900566 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400567 self.assertTrue('unexpected' in str(e))
568
Joe Gregoriobee86832011-02-22 10:00:19 -0500569 def _check_query_types(self, request):
Pat Ferated5b61bd2015-03-03 16:04:11 -0800570 parsed = urlparse(request.uri)
Joe Gregoriobee86832011-02-22 10:00:19 -0500571 q = parse_qs(parsed[4])
572 self.assertEqual(q['q'], ['foo'])
573 self.assertEqual(q['i'], ['1'])
574 self.assertEqual(q['n'], ['1.0'])
575 self.assertEqual(q['b'], ['false'])
576 self.assertEqual(q['a'], ['[1, 2, 3]'])
577 self.assertEqual(q['o'], ['{\'a\': 1}'])
578 self.assertEqual(q['e'], ['bar'])
579
580 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400581 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400582 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500583
Joe Gregorioa98733f2011-09-16 10:12:28 -0400584 request = zoo.query(
585 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 -0500586 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400587 request = zoo.query(
588 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500589 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500590
Joe Gregorioa98733f2011-09-16 10:12:28 -0400591 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800592 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 -0500593
594 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800595 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
596 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500597 self._check_query_types(request)
598
Craig Citro1e742822012-03-01 12:59:22 -0800599 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500600 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800601
Joe Gregorio13217952011-02-22 15:37:38 -0500602 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400603 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400604 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400605 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500606
Pat Ferated5b61bd2015-03-03 16:04:11 -0800607 parsed = urlparse(request.uri)
Joe Gregorioca876e42011-02-22 19:39:42 -0500608 q = parse_qs(parsed[4])
609 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400610 self.assertEqual(q['fields'], ['description'])
611
Joe Gregorio2467afa2012-06-20 12:21:25 -0400612 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400613 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400614 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400615 request = zoo.query(trace=None, fields='description')
616
Pat Ferated5b61bd2015-03-03 16:04:11 -0800617 parsed = urlparse(request.uri)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400618 q = parse_qs(parsed[4])
619 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400620
Joe Gregorioe08a1662011-12-07 09:48:22 -0500621 def test_model_added_query_parameters(self):
622 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400623 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500624 request = zoo.animals().get(name='Lion')
625
Pat Ferated5b61bd2015-03-03 16:04:11 -0800626 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500627 q = parse_qs(parsed[4])
628 self.assertEqual(q['alt'], ['json'])
629 self.assertEqual(request.headers['accept'], 'application/json')
630
631 def test_fallback_to_raw_model(self):
632 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400633 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500634 request = zoo.animals().getmedia(name='Lion')
635
Pat Ferated5b61bd2015-03-03 16:04:11 -0800636 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500637 q = parse_qs(parsed[4])
638 self.assertTrue('alt' not in q)
639 self.assertEqual(request.headers['accept'], '*/*')
640
Joe Gregoriof4153422011-03-18 22:45:18 -0400641 def test_patch(self):
642 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400643 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400644 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
645
646 self.assertEqual(request.method, 'PATCH')
647
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -0400648 def test_batch_request_from_discovery(self):
649 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
650 # zoo defines a batchPath
651 zoo = build('zoo', 'v1', http=self.http)
652 batch_request = zoo.new_batch_http_request()
653 self.assertEqual(batch_request._batch_uri,
654 "https://www.googleapis.com/batchZoo")
655
656 def test_batch_request_from_default(self):
657 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
658 # plus does not define a batchPath
659 plus = build('plus', 'v1', http=self.http)
660 batch_request = plus.new_batch_http_request()
661 self.assertEqual(batch_request._batch_uri,
662 "https://www.googleapis.com/batch")
663
Joe Gregoriof4153422011-03-18 22:45:18 -0400664 def test_tunnel_patch(self):
665 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400666 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400667 ({'status': '200'}, 'echo_request_headers_as_json'),
668 ])
669 http = tunnel_patch(http)
Takashi Matsuo30125122015-08-19 11:42:32 -0700670 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400671 resp = zoo.animals().patch(
672 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400673
674 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500675
Joe Gregorio7b70f432011-11-09 10:18:51 -0500676 def test_plus_resources(self):
677 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400678 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500679 self.assertTrue(getattr(plus, 'activities'))
680 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400681
Orest Bolohane92c9002014-05-30 11:15:43 -0700682 def test_credentials(self):
683 class CredentialsMock:
684 def create_scoped_required(self):
685 return False
686
687 def authorize(self, http):
688 http.orest = True
689
690 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
691 build('plus', 'v1', http=self.http, credentials=None)
692 self.assertFalse(hasattr(self.http, 'orest'))
693 build('plus', 'v1', http=self.http, credentials=CredentialsMock())
694 self.assertTrue(hasattr(self.http, 'orest'))
695
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400696 def test_full_featured(self):
697 # Zoo should exercise all discovery facets
698 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500699 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400700 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400701 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500702
Joe Gregoriof4153422011-03-18 22:45:18 -0400703 request = zoo.animals().list(name='bat', projection="full")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800704 parsed = urlparse(request.uri)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400705 q = parse_qs(parsed[4])
706 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400707 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400708
Joe Gregorio3fada332011-01-07 17:07:45 -0500709 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500710 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400711 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500712 self.assertTrue(getattr(zoo, 'animals'))
713 request = zoo.my().favorites().list(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800714 parsed = urlparse(request.uri)
Joe Gregorio3fada332011-01-07 17:07:45 -0500715 q = parse_qs(parsed[4])
716 self.assertEqual(q['max-results'], ['5'])
717
Pat Feratec6050872015-03-03 18:24:59 -0800718 @unittest.skipIf(six.PY3, 'print is not a reserved name in Python 3')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400719 def test_methods_with_reserved_names(self):
720 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400721 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400722 self.assertTrue(getattr(zoo, 'animals'))
723 request = zoo.global_().print_().assert_(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800724 parsed = urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400725 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400726
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500727 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500728 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400729 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500730 self.assertTrue(getattr(zoo, 'query'))
731 request = zoo.query(q="foo")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800732 parsed = urlparse(request.uri)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500733 q = parse_qs(parsed[4])
734 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400735
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400736 def test_simple_media_uploads(self):
737 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400738 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400739 doc = getattr(zoo.animals().insert, '__doc__')
740 self.assertTrue('media_body' in doc)
741
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400742 def test_simple_media_upload_no_max_size_provided(self):
743 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400744 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400745 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
746 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800747 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400748
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400749 def test_simple_media_raise_correct_exceptions(self):
750 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400751 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400752
753 try:
754 zoo.animals().insert(media_body=datafile('smiley.png'))
755 self.fail("should throw exception if media is too large.")
756 except MediaUploadSizeError:
757 pass
758
759 try:
760 zoo.animals().insert(media_body=datafile('small.jpg'))
761 self.fail("should throw exception if mimetype is unacceptable.")
762 except UnacceptableMimeTypeError:
763 pass
764
765 def test_simple_media_good_upload(self):
766 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400767 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400768
769 request = zoo.animals().insert(media_body=datafile('small.png'))
770 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800771 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500772 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400773 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500774 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400775
776 def test_multipart_media_raise_correct_exceptions(self):
777 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400778 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400779
780 try:
781 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
782 self.fail("should throw exception if media is too large.")
783 except MediaUploadSizeError:
784 pass
785
786 try:
787 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
788 self.fail("should throw exception if mimetype is unacceptable.")
789 except UnacceptableMimeTypeError:
790 pass
791
792 def test_multipart_media_good_upload(self):
793 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400794 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400795
796 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400797 self.assertTrue(request.headers['content-type'].startswith(
798 'multipart/related'))
Phil Ruffwind26178fc2015-10-13 19:00:33 -0400799 with open(datafile('small.png'), 'rb') as f:
800 contents = f.read()
801 boundary = re.match(b'--=+([^=]+)', request.body).group(1)
802 self.assertEqual(
803 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
804 b'--===============' + boundary + b'==\n' +
805 b'Content-Type: application/json\n' +
806 b'MIME-Version: 1.0\n\n' +
807 b'{"data": {}}\n' +
808 b'--===============' + boundary + b'==\n' +
809 b'Content-Type: image/png\n' +
810 b'MIME-Version: 1.0\n' +
811 b'Content-Transfer-Encoding: binary\n\n' +
812 contents +
813 b'\n--===============' + boundary + b'==--')
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500814 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400815 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500816 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400817
818 def test_media_capable_method_without_media(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(body={})
823 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400824
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500825 def test_resumable_multipart_media_good_upload(self):
826 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400827 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500828
829 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
830 request = zoo.animals().insert(media_body=media_upload, body={})
831 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500832 'application/json'))
833 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500834 self.assertEquals(media_upload, request.resumable)
835
836 self.assertEquals('image/png', request.resumable.mimetype())
837
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500838 self.assertNotEquals(request.body, None)
839 self.assertEquals(request.resumable_uri, None)
840
841 http = HttpMockSequence([
842 ({'status': '200',
843 'location': 'http://upload.example.com'}, ''),
844 ({'status': '308',
845 'location': 'http://upload.example.com/2',
846 'range': '0-12'}, ''),
847 ({'status': '308',
848 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500849 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500850 ({'status': '200'}, '{"foo": "bar"}'),
851 ])
852
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400853 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500854 self.assertEquals(None, body)
855 self.assertTrue(isinstance(status, MediaUploadProgress))
856 self.assertEquals(13, status.resumable_progress)
857
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500858 # Two requests should have been made and the resumable_uri should have been
859 # updated for each one.
860 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
861
862 self.assertEquals(media_upload, request.resumable)
863 self.assertEquals(13, request.resumable_progress)
864
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400865 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500866 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500867 self.assertEquals(media_upload.size()-1, request.resumable_progress)
868 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500869
870 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400871 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500872 self.assertEquals(body, {"foo": "bar"})
873 self.assertEquals(status, None)
874
875
876 def test_resumable_media_good_upload(self):
877 """Not a multipart upload."""
878 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400879 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500880
881 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
882 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500883 self.assertEquals(media_upload, request.resumable)
884
885 self.assertEquals('image/png', request.resumable.mimetype())
886
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500887 self.assertEquals(request.body, None)
888 self.assertEquals(request.resumable_uri, None)
889
890 http = HttpMockSequence([
891 ({'status': '200',
892 'location': 'http://upload.example.com'}, ''),
893 ({'status': '308',
894 'location': 'http://upload.example.com/2',
895 'range': '0-12'}, ''),
896 ({'status': '308',
897 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500898 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500899 ({'status': '200'}, '{"foo": "bar"}'),
900 ])
901
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400902 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500903 self.assertEquals(None, body)
904 self.assertTrue(isinstance(status, MediaUploadProgress))
905 self.assertEquals(13, status.resumable_progress)
906
907 # Two requests should have been made and the resumable_uri should have been
908 # updated for each one.
909 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
910
911 self.assertEquals(media_upload, request.resumable)
912 self.assertEquals(13, request.resumable_progress)
913
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400914 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500915 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500916 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500917 self.assertEquals(request.body, None)
918
919 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400920 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500921 self.assertEquals(body, {"foo": "bar"})
922 self.assertEquals(status, None)
923
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500924 def test_resumable_media_good_upload_from_execute(self):
925 """Not a multipart upload."""
926 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400927 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500928
929 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
930 request = zoo.animals().insert(media_body=media_upload, body=None)
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=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500933 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500934
935 http = HttpMockSequence([
936 ({'status': '200',
937 'location': 'http://upload.example.com'}, ''),
938 ({'status': '308',
939 'location': 'http://upload.example.com/2',
940 'range': '0-12'}, ''),
941 ({'status': '308',
942 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500943 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500944 ({'status': '200'}, '{"foo": "bar"}'),
945 ])
946
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400947 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500948 self.assertEquals(body, {"foo": "bar"})
949
950 def test_resumable_media_fail_unknown_response_code_first_request(self):
951 """Not a multipart upload."""
952 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400953 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500954
955 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
956 request = zoo.animals().insert(media_body=media_upload, body=None)
957
958 http = HttpMockSequence([
959 ({'status': '400',
960 'location': 'http://upload.example.com'}, ''),
961 ])
962
Joe Gregoriobaf04802013-03-01 12:27:06 -0500963 try:
964 request.execute(http=http)
965 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +0900966 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -0500967 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500968
969 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
970 """Not a multipart upload."""
971 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400972 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500973
974 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
975 request = zoo.animals().insert(media_body=media_upload, body=None)
976
977 http = HttpMockSequence([
978 ({'status': '200',
979 'location': 'http://upload.example.com'}, ''),
980 ({'status': '400'}, ''),
981 ])
982
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400983 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400984 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500985
Joe Gregorio910b9b12012-06-12 09:36:30 -0400986 http = HttpMockSequence([
987 ({'status': '308',
988 'range': '0-5'}, ''),
989 ({'status': '308',
990 'range': '0-6'}, ''),
991 ])
992
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400993 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400994 self.assertEquals(status.resumable_progress, 7,
995 'Should have first checked length and then tried to PUT more.')
996 self.assertFalse(request._in_error_state)
997
998 # Put it back in an error state.
999 http = HttpMockSequence([
1000 ({'status': '400'}, ''),
1001 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001002 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001003 self.assertTrue(request._in_error_state)
1004
1005 # Pretend the last request that 400'd actually succeeded.
1006 http = HttpMockSequence([
1007 ({'status': '200'}, '{"foo": "bar"}'),
1008 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001009 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001010 self.assertEqual(body, {'foo': 'bar'})
1011
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001012 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1013 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1014 zoo = build('zoo', 'v1', http=self.http)
1015
Pat Ferateed9affd2015-03-03 16:03:15 -08001016 # Set up a seekable stream and try to upload in single chunk.
Pat Ferate2b140222015-03-03 18:05:11 -08001017 fd = BytesIO(b'01234"56789"')
Pat Ferateed9affd2015-03-03 16:03:15 -08001018 media_upload = MediaIoBaseUpload(
1019 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001020
Pat Ferateed9affd2015-03-03 16:03:15 -08001021 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001022
Pat Ferateed9affd2015-03-03 16:03:15 -08001023 # The single chunk fails, restart at the right point.
1024 http = HttpMockSequence([
1025 ({'status': '200',
1026 'location': 'http://upload.example.com'}, ''),
1027 ({'status': '308',
1028 'location': 'http://upload.example.com/2',
1029 'range': '0-4'}, ''),
1030 ({'status': '200'}, 'echo_request_body'),
1031 ])
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001032
Pat Ferateed9affd2015-03-03 16:03:15 -08001033 body = request.execute(http=http)
1034 self.assertEqual('56789', body)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001035
1036 def test_media_io_base_stream_chunksize_resume(self):
1037 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1038 zoo = build('zoo', 'v1', http=self.http)
1039
Pat Ferateed9affd2015-03-03 16:03:15 -08001040 # Set up a seekable stream and try to upload in chunks.
Pat Ferate2b140222015-03-03 18:05:11 -08001041 fd = BytesIO(b'0123456789')
Pat Ferateed9affd2015-03-03 16:03:15 -08001042 media_upload = MediaIoBaseUpload(
1043 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
1044
1045 request = zoo.animals().insert(media_body=media_upload, body=None)
1046
1047 # The single chunk fails, pull the content sent out of the exception.
1048 http = HttpMockSequence([
1049 ({'status': '200',
1050 'location': 'http://upload.example.com'}, ''),
1051 ({'status': '400'}, 'echo_request_body'),
1052 ])
1053
Joe Gregorio5c120db2012-08-23 09:13:55 -04001054 try:
Pat Ferateed9affd2015-03-03 16:03:15 -08001055 body = request.execute(http=http)
1056 except HttpError as e:
Pat Ferate2b140222015-03-03 18:05:11 -08001057 self.assertEqual(b'01234', e.content)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001058
Joe Gregorio910b9b12012-06-12 09:36:30 -04001059 def test_resumable_media_handle_uploads_of_unknown_size(self):
1060 http = HttpMockSequence([
1061 ({'status': '200',
1062 'location': 'http://upload.example.com'}, ''),
1063 ({'status': '200'}, 'echo_request_headers_as_json'),
1064 ])
1065
1066 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001067 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001068
Joe Gregorio910b9b12012-06-12 09:36:30 -04001069 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001070 class IoBaseUnknownLength(MediaUpload):
1071 def chunksize(self):
1072 return 10
1073
1074 def mimetype(self):
1075 return 'image/png'
1076
1077 def size(self):
1078 return None
1079
1080 def resumable(self):
1081 return True
1082
1083 def getbytes(self, begin, length):
1084 return '0123456789'
1085
1086 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -04001087
1088 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001089 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001090 self.assertEqual(body, {
1091 'Content-Range': 'bytes 0-9/*',
1092 'Content-Length': '10',
1093 })
Joe Gregorio44454e42012-06-15 08:38:53 -04001094
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001095 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1096 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1097 zoo = build('zoo', 'v1', http=self.http)
1098
1099 class IoBaseHasStream(MediaUpload):
1100 def chunksize(self):
1101 return 10
1102
1103 def mimetype(self):
1104 return 'image/png'
1105
1106 def size(self):
1107 return None
1108
1109 def resumable(self):
1110 return True
1111
1112 def getbytes(self, begin, length):
1113 return '0123456789'
1114
1115 def has_stream(self):
1116 return True
1117
1118 def stream(self):
1119 raise NotImplementedError()
1120
1121 upload = IoBaseHasStream()
1122
1123 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001124
1125 sys.version_info = (2, 6, 5, 'final', 0)
1126
1127 request = zoo.animals().insert(media_body=upload, body=None)
1128
1129 # This should raise an exception because stream() will be called.
1130 http = HttpMockSequence([
1131 ({'status': '200',
1132 'location': 'http://upload.example.com'}, ''),
1133 ({'status': '200'}, 'echo_request_headers_as_json'),
1134 ])
1135
1136 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
1137
1138 sys.version_info = orig_version
1139
Joe Gregorio44454e42012-06-15 08:38:53 -04001140 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1141 http = HttpMockSequence([
1142 ({'status': '200',
1143 'location': 'http://upload.example.com'}, ''),
1144 ({'status': '200'}, 'echo_request_headers_as_json'),
1145 ])
1146
1147 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001148 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001149
Pat Ferate2b140222015-03-03 18:05:11 -08001150 fd = BytesIO(b'data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001151
1152 # Create an upload that doesn't know the full size of the media.
1153 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001154 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001155
1156 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001157 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001158 self.assertEqual(body, {
1159 'Content-Range': 'bytes 0-13/14',
1160 'Content-Length': '14',
1161 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001162
1163 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1164 http = HttpMockSequence([
1165 ({'status': '200',
1166 'location': 'http://upload.example.com'}, ''),
1167 ({'status': '400'}, ''),
1168 ])
1169
1170 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001171 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001172
1173 # Create an upload that doesn't know the full size of the media.
Pat Ferate2b140222015-03-03 18:05:11 -08001174 fd = BytesIO(b'data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001175
1176 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001177 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001178
1179 request = zoo.animals().insert(media_body=upload, body=None)
1180
1181 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001182 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001183
1184 http = HttpMockSequence([
1185 ({'status': '400',
1186 'range': '0-5'}, 'echo_request_headers_as_json'),
1187 ])
1188 try:
1189 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001190 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001191 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001192 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001193 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001194 'content-length': '0'
1195 }
INADA Naoki09157612015-03-25 01:51:03 +09001196 self.assertEqual(expected, json.loads(e.content.decode('utf-8')),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001197 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001198
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001199 def test_pickle(self):
1200 sorted_resource_keys = ['_baseUrl',
1201 '_developerKey',
1202 '_dynamic_attrs',
1203 '_http',
1204 '_model',
1205 '_requestBuilder',
1206 '_resourceDesc',
1207 '_rootDesc',
1208 '_schema',
1209 'animals',
1210 'global_',
1211 'load',
1212 'loadNoTemplate',
1213 'my',
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001214 'new_batch_http_request',
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001215 'query',
1216 'scopedAnimals']
1217
1218 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1219 zoo = build('zoo', 'v1', http=http)
1220 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1221
1222 pickled_zoo = pickle.dumps(zoo)
1223 new_zoo = pickle.loads(pickled_zoo)
1224 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1225 self.assertTrue(hasattr(new_zoo, 'animals'))
1226 self.assertTrue(callable(new_zoo.animals))
1227 self.assertTrue(hasattr(new_zoo, 'global_'))
1228 self.assertTrue(callable(new_zoo.global_))
1229 self.assertTrue(hasattr(new_zoo, 'load'))
1230 self.assertTrue(callable(new_zoo.load))
1231 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1232 self.assertTrue(callable(new_zoo.loadNoTemplate))
1233 self.assertTrue(hasattr(new_zoo, 'my'))
1234 self.assertTrue(callable(new_zoo.my))
1235 self.assertTrue(hasattr(new_zoo, 'query'))
1236 self.assertTrue(callable(new_zoo.query))
1237 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1238 self.assertTrue(callable(new_zoo.scopedAnimals))
1239
Joe Gregorio003b6e42013-02-13 15:42:19 -05001240 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001241 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1242 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1243 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1244 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1245 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1246 # _http, _model and _schema won't be equal since we will get new
1247 # instances upon un-pickling
1248
1249 def _dummy_zoo_request(self):
1250 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1251 zoo_contents = fh.read()
1252
1253 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1254 {'api': 'zoo', 'apiVersion': 'v1'})
1255 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001256 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1257 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001258
1259 http = httplib2.Http()
1260 original_request = http.request
1261 def wrapped_request(uri, method='GET', *args, **kwargs):
1262 if uri == zoo_uri:
1263 return httplib2.Response({'status': '200'}), zoo_contents
1264 return original_request(uri, method=method, *args, **kwargs)
1265 http.request = wrapped_request
1266 return http
1267
1268 def _dummy_token(self):
1269 access_token = 'foo'
1270 client_id = 'some_client_id'
1271 client_secret = 'cOuDdkfjxxnv+'
1272 refresh_token = '1/0/a.df219fjls0'
1273 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001274 user_agent = 'refresh_checker/1.0'
1275 return OAuth2Credentials(
1276 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001277 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001278 user_agent)
1279
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001280 def test_pickle_with_credentials(self):
1281 credentials = self._dummy_token()
1282 http = self._dummy_zoo_request()
1283 http = credentials.authorize(http)
1284 self.assertTrue(hasattr(http.request, 'credentials'))
1285
1286 zoo = build('zoo', 'v1', http=http)
1287 pickled_zoo = pickle.dumps(zoo)
1288 new_zoo = pickle.loads(pickled_zoo)
1289 self.assertEqual(sorted(zoo.__dict__.keys()),
1290 sorted(new_zoo.__dict__.keys()))
1291 new_http = new_zoo._http
1292 self.assertFalse(hasattr(new_http.request, 'credentials'))
1293
Joe Gregorio708388c2012-06-15 13:43:04 -04001294
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001295class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001296
Joe Gregorio3c676f92011-07-25 10:38:14 -04001297 def test_next_successful_none_on_no_next_page_token(self):
1298 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001299 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001300 request = tasks.tasklists().list()
1301 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1302
Son Dinh2a9a2132015-07-23 16:30:56 +00001303 def test_next_successful_none_on_empty_page_token(self):
1304 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
1305 tasks = build('tasks', 'v1', http=self.http)
1306 request = tasks.tasklists().list()
1307 next_request = tasks.tasklists().list_next(
1308 request, {'nextPageToken': ''})
1309 self.assertEqual(None, next_request)
1310
Joe Gregorio3c676f92011-07-25 10:38:14 -04001311 def test_next_successful_with_next_page_token(self):
1312 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001313 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001314 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001315 next_request = tasks.tasklists().list_next(
1316 request, {'nextPageToken': '123abc'})
Pat Ferated5b61bd2015-03-03 16:04:11 -08001317 parsed = list(urlparse(next_request.uri))
Joe Gregorio3c676f92011-07-25 10:38:14 -04001318 q = parse_qs(parsed[4])
1319 self.assertEqual(q['pageToken'][0], '123abc')
1320
Joe Gregorio555f33c2011-08-19 14:56:07 -04001321 def test_next_with_method_with_no_properties(self):
1322 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001323 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001324 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001325
Joe Gregorioa98733f2011-09-16 10:12:28 -04001326
Joe Gregorio708388c2012-06-15 13:43:04 -04001327class MediaGet(unittest.TestCase):
1328
1329 def test_get_media(self):
1330 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001331 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001332 request = zoo.animals().get_media(name='Lion')
1333
Pat Ferated5b61bd2015-03-03 16:04:11 -08001334 parsed = urlparse(request.uri)
Joe Gregorio708388c2012-06-15 13:43:04 -04001335 q = parse_qs(parsed[4])
1336 self.assertEqual(q['alt'], ['media'])
1337 self.assertEqual(request.headers['accept'], '*/*')
1338
1339 http = HttpMockSequence([
1340 ({'status': '200'}, 'standing in for media'),
1341 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001342 response = request.execute(http=http)
INADA Naoki09157612015-03-25 01:51:03 +09001343 self.assertEqual(b'standing in for media', response)
Joe Gregorio708388c2012-06-15 13:43:04 -04001344
1345
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001346if __name__ == '__main__':
1347 unittest.main()