blob: 0181bbb7e62c79ad08249dadca89b76c8a98a4d3 [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()),
355 ])
356 with self.assertRaises(UnknownApiNameOrVersion):
357 plus = build('plus', 'v1', http=http, cache_discovery=False)
358
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500359
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100360class DiscoveryFromDocument(unittest.TestCase):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400361
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100362 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400363 discovery = open(datafile('plus.json')).read()
Joe Gregorio7b70f432011-11-09 10:18:51 -0500364 plus = build_from_document(discovery, base="https://www.googleapis.com/")
365 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500366 self.assertTrue(hasattr(plus, 'activities'))
367
368 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400369 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700370 discovery = json.loads(discovery)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500371 plus = build_from_document(discovery, base="https://www.googleapis.com/")
372 self.assertTrue(plus is not None)
373 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400374
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100375 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400376 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400377
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100378 base = "https://www.example.com/"
Joe Gregorio7b70f432011-11-09 10:18:51 -0500379 plus = build_from_document(discovery, base=base)
Joe Gregorioa2838152012-07-16 11:52:17 -0400380 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100381
Jonathan Wayne Parrotta6e6fbd2015-07-16 15:33:57 -0700382 def test_building_with_optional_http(self):
383 discovery = open(datafile('plus.json')).read()
384 plus = build_from_document(discovery, base="https://www.googleapis.com/")
385 self.assertTrue(isinstance(plus._http, httplib2.Http))
386
387 def test_building_with_explicit_http(self):
388 http = HttpMock()
389 discovery = open(datafile('plus.json')).read()
390 plus = build_from_document(
391 discovery, base="https://www.googleapis.com/", http=http)
392 self.assertEquals(plus._http, http)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100393
Joe Gregorioa98733f2011-09-16 10:12:28 -0400394class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400395 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700396 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400397
Joe Gregorio583d9e42011-09-16 15:54:15 -0400398 def tearDown(self):
399 os.environ = self.old_environ
400
401 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400402 # build() will raise an HttpError on a 400, use this to pick the request uri
403 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400404 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400405 try:
406 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400407 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400408 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400409 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400410 discoveryServiceUrl='http://example.com')
411 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900412 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400413 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400414
Joe Gregorio583d9e42011-09-16 15:54:15 -0400415 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400416 # build() will raise an HttpError on a 400, use this to pick the request uri
417 # out of the raised exception.
418 try:
419 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400420 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400421 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400422 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400423 discoveryServiceUrl='http://example.com')
424 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900425 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400426 self.assertEqual(e.uri, 'http://example.com')
427
428
Takashi Matsuo30125122015-08-19 11:42:32 -0700429class DiscoveryFromAppEngineCache(unittest.TestCase):
430 def test_appengine_memcache(self):
431 # Hack module import
432 self.orig_import = __import__
433 self.mocked_api = mock.MagicMock()
434
435 def import_mock(name, *args):
436 if name == 'google.appengine.api':
437 return self.mocked_api
438 return self.orig_import(name, *args)
439
440 import_fullname = '__builtin__.__import__'
441 if sys.version_info[0] >= 3:
442 import_fullname = 'builtins.__import__'
443
444 with mock.patch(import_fullname, side_effect=import_mock):
445 namespace = 'google-api-client'
446 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
447
448 self.mocked_api.memcache.get.return_value = None
449
450 plus = build('plus', 'v1', http=self.http)
451
452 # memcache.get is called once
453 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
454 self.mocked_api.memcache.get.assert_called_once_with(url,
455 namespace=namespace)
456
457 # memcache.set is called once
458 with open(datafile('plus.json')) as f:
459 content = f.read()
460 self.mocked_api.memcache.set.assert_called_once_with(
461 url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace)
462
463 # Returns the cached content this time.
464 self.mocked_api.memcache.get.return_value = content
465
466 # Make sure the contents are returned from the cache.
467 # (Otherwise it should through an error)
468 self.http = HttpMock(None, {'status': '200'})
469
470 plus = build('plus', 'v1', http=self.http)
471
472 # memcache.get is called twice
473 self.mocked_api.memcache.get.assert_has_calls(
474 [mock.call(url, namespace=namespace),
475 mock.call(url, namespace=namespace)])
476
477 # memcahce.set is called just once
478 self.mocked_api.memcache.set.assert_called_once_with(
479 url, content, time=DISCOVERY_DOC_MAX_AGE,namespace=namespace)
480
481
482class DictCache(Cache):
483 def __init__(self):
484 self.d = {}
485 def get(self, url):
486 return self.d.get(url, None)
487 def set(self, url, content):
488 self.d[url] = content
489 def contains(self, url):
490 return url in self.d
491
492
493class DiscoveryFromFileCache(unittest.TestCase):
494 def test_file_based_cache(self):
495 cache = mock.Mock(wraps=DictCache())
496 with mock.patch('googleapiclient.discovery_cache.file_cache.cache',
497 new=cache):
498 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
499
500 plus = build('plus', 'v1', http=self.http)
501
502 # cache.get is called once
503 url = 'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
504 cache.get.assert_called_once_with(url)
505
506 # cache.set is called once
507 with open(datafile('plus.json')) as f:
508 content = f.read()
509 cache.set.assert_called_once_with(url, content)
510
511 # Make sure there is a cache entry for the plus v1 discovery doc.
512 self.assertTrue(cache.contains(url))
513
514 # Make sure the contents are returned from the cache.
515 # (Otherwise it should through an error)
516 self.http = HttpMock(None, {'status': '200'})
517
518 plus = build('plus', 'v1', http=self.http)
519
520 # cache.get is called twice
521 cache.get.assert_has_calls([mock.call(url), mock.call(url)])
522
523 # cahce.set is called just once
524 cache.set.assert_called_once_with(url, content)
525
526
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400527class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400528
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400529 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500530 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400531 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400532
533 # Missing required parameters
534 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500535 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400536 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900537 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400538 self.assertTrue('Missing' in str(e))
539
Joe Gregorio2467afa2012-06-20 12:21:25 -0400540 # Missing required parameters even if supplied as None.
541 try:
542 plus.activities().list(collection=None, userId=None)
543 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900544 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400545 self.assertTrue('Missing' in str(e))
546
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400547 # Parameter doesn't match regex
548 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500549 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400550 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900551 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500552 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400553
554 # Unexpected parameter
555 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500556 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400557 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900558 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400559 self.assertTrue('unexpected' in str(e))
560
Joe Gregoriobee86832011-02-22 10:00:19 -0500561 def _check_query_types(self, request):
Pat Ferated5b61bd2015-03-03 16:04:11 -0800562 parsed = urlparse(request.uri)
Joe Gregoriobee86832011-02-22 10:00:19 -0500563 q = parse_qs(parsed[4])
564 self.assertEqual(q['q'], ['foo'])
565 self.assertEqual(q['i'], ['1'])
566 self.assertEqual(q['n'], ['1.0'])
567 self.assertEqual(q['b'], ['false'])
568 self.assertEqual(q['a'], ['[1, 2, 3]'])
569 self.assertEqual(q['o'], ['{\'a\': 1}'])
570 self.assertEqual(q['e'], ['bar'])
571
572 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400573 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400574 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500575
Joe Gregorioa98733f2011-09-16 10:12:28 -0400576 request = zoo.query(
577 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 -0500578 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400579 request = zoo.query(
580 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500581 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500582
Joe Gregorioa98733f2011-09-16 10:12:28 -0400583 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800584 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 -0500585
586 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800587 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
588 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500589 self._check_query_types(request)
590
Craig Citro1e742822012-03-01 12:59:22 -0800591 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500592 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800593
Joe Gregorio13217952011-02-22 15:37:38 -0500594 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400595 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400596 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400597 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500598
Pat Ferated5b61bd2015-03-03 16:04:11 -0800599 parsed = urlparse(request.uri)
Joe Gregorioca876e42011-02-22 19:39:42 -0500600 q = parse_qs(parsed[4])
601 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400602 self.assertEqual(q['fields'], ['description'])
603
Joe Gregorio2467afa2012-06-20 12:21:25 -0400604 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400605 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400606 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400607 request = zoo.query(trace=None, fields='description')
608
Pat Ferated5b61bd2015-03-03 16:04:11 -0800609 parsed = urlparse(request.uri)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400610 q = parse_qs(parsed[4])
611 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400612
Joe Gregorioe08a1662011-12-07 09:48:22 -0500613 def test_model_added_query_parameters(self):
614 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400615 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500616 request = zoo.animals().get(name='Lion')
617
Pat Ferated5b61bd2015-03-03 16:04:11 -0800618 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500619 q = parse_qs(parsed[4])
620 self.assertEqual(q['alt'], ['json'])
621 self.assertEqual(request.headers['accept'], 'application/json')
622
623 def test_fallback_to_raw_model(self):
624 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400625 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500626 request = zoo.animals().getmedia(name='Lion')
627
Pat Ferated5b61bd2015-03-03 16:04:11 -0800628 parsed = urlparse(request.uri)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500629 q = parse_qs(parsed[4])
630 self.assertTrue('alt' not in q)
631 self.assertEqual(request.headers['accept'], '*/*')
632
Joe Gregoriof4153422011-03-18 22:45:18 -0400633 def test_patch(self):
634 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400635 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400636 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
637
638 self.assertEqual(request.method, 'PATCH')
639
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -0400640 def test_batch_request_from_discovery(self):
641 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
642 # zoo defines a batchPath
643 zoo = build('zoo', 'v1', http=self.http)
644 batch_request = zoo.new_batch_http_request()
645 self.assertEqual(batch_request._batch_uri,
646 "https://www.googleapis.com/batchZoo")
647
648 def test_batch_request_from_default(self):
649 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
650 # plus does not define a batchPath
651 plus = build('plus', 'v1', http=self.http)
652 batch_request = plus.new_batch_http_request()
653 self.assertEqual(batch_request._batch_uri,
654 "https://www.googleapis.com/batch")
655
Joe Gregoriof4153422011-03-18 22:45:18 -0400656 def test_tunnel_patch(self):
657 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400658 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400659 ({'status': '200'}, 'echo_request_headers_as_json'),
660 ])
661 http = tunnel_patch(http)
Takashi Matsuo30125122015-08-19 11:42:32 -0700662 zoo = build('zoo', 'v1', http=http, cache_discovery=False)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400663 resp = zoo.animals().patch(
664 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400665
666 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500667
Joe Gregorio7b70f432011-11-09 10:18:51 -0500668 def test_plus_resources(self):
669 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400670 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500671 self.assertTrue(getattr(plus, 'activities'))
672 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400673
Orest Bolohane92c9002014-05-30 11:15:43 -0700674 def test_credentials(self):
675 class CredentialsMock:
676 def create_scoped_required(self):
677 return False
678
679 def authorize(self, http):
680 http.orest = True
681
682 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
683 build('plus', 'v1', http=self.http, credentials=None)
684 self.assertFalse(hasattr(self.http, 'orest'))
685 build('plus', 'v1', http=self.http, credentials=CredentialsMock())
686 self.assertTrue(hasattr(self.http, 'orest'))
687
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400688 def test_full_featured(self):
689 # Zoo should exercise all discovery facets
690 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500691 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400692 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400693 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500694
Joe Gregoriof4153422011-03-18 22:45:18 -0400695 request = zoo.animals().list(name='bat', projection="full")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800696 parsed = urlparse(request.uri)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400697 q = parse_qs(parsed[4])
698 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400699 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400700
Joe Gregorio3fada332011-01-07 17:07:45 -0500701 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500702 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400703 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500704 self.assertTrue(getattr(zoo, 'animals'))
705 request = zoo.my().favorites().list(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800706 parsed = urlparse(request.uri)
Joe Gregorio3fada332011-01-07 17:07:45 -0500707 q = parse_qs(parsed[4])
708 self.assertEqual(q['max-results'], ['5'])
709
Pat Feratec6050872015-03-03 18:24:59 -0800710 @unittest.skipIf(six.PY3, 'print is not a reserved name in Python 3')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400711 def test_methods_with_reserved_names(self):
712 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400713 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400714 self.assertTrue(getattr(zoo, 'animals'))
715 request = zoo.global_().print_().assert_(max_results="5")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800716 parsed = urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400717 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400718
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500719 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500720 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400721 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500722 self.assertTrue(getattr(zoo, 'query'))
723 request = zoo.query(q="foo")
Pat Ferated5b61bd2015-03-03 16:04:11 -0800724 parsed = urlparse(request.uri)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500725 q = parse_qs(parsed[4])
726 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400727
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400728 def test_simple_media_uploads(self):
729 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400730 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400731 doc = getattr(zoo.animals().insert, '__doc__')
732 self.assertTrue('media_body' in doc)
733
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400734 def test_simple_media_upload_no_max_size_provided(self):
735 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400736 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400737 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
738 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800739 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400740
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400741 def test_simple_media_raise_correct_exceptions(self):
742 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400743 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400744
745 try:
746 zoo.animals().insert(media_body=datafile('smiley.png'))
747 self.fail("should throw exception if media is too large.")
748 except MediaUploadSizeError:
749 pass
750
751 try:
752 zoo.animals().insert(media_body=datafile('small.jpg'))
753 self.fail("should throw exception if mimetype is unacceptable.")
754 except UnacceptableMimeTypeError:
755 pass
756
757 def test_simple_media_good_upload(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 request = zoo.animals().insert(media_body=datafile('small.png'))
762 self.assertEquals('image/png', request.headers['content-type'])
Pat Ferate2b140222015-03-03 18:05:11 -0800763 self.assertEquals(b'PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500764 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400765 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500766 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400767
768 def test_multipart_media_raise_correct_exceptions(self):
769 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400770 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400771
772 try:
773 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
774 self.fail("should throw exception if media is too large.")
775 except MediaUploadSizeError:
776 pass
777
778 try:
779 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
780 self.fail("should throw exception if mimetype is unacceptable.")
781 except UnacceptableMimeTypeError:
782 pass
783
784 def test_multipart_media_good_upload(self):
785 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400786 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400787
788 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400789 self.assertTrue(request.headers['content-type'].startswith(
790 'multipart/related'))
Phil Ruffwind26178fc2015-10-13 19:00:33 -0400791 with open(datafile('small.png'), 'rb') as f:
792 contents = f.read()
793 boundary = re.match(b'--=+([^=]+)', request.body).group(1)
794 self.assertEqual(
795 request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n
796 b'--===============' + boundary + b'==\n' +
797 b'Content-Type: application/json\n' +
798 b'MIME-Version: 1.0\n\n' +
799 b'{"data": {}}\n' +
800 b'--===============' + boundary + b'==\n' +
801 b'Content-Type: image/png\n' +
802 b'MIME-Version: 1.0\n' +
803 b'Content-Transfer-Encoding: binary\n\n' +
804 contents +
805 b'\n--===============' + boundary + b'==--')
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500806 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400807 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500808 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400809
810 def test_media_capable_method_without_media(self):
811 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400812 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400813
814 request = zoo.animals().insert(body={})
815 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400816
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500817 def test_resumable_multipart_media_good_upload(self):
818 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400819 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500820
821 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
822 request = zoo.animals().insert(media_body=media_upload, body={})
823 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500824 'application/json'))
825 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500826 self.assertEquals(media_upload, request.resumable)
827
828 self.assertEquals('image/png', request.resumable.mimetype())
829
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500830 self.assertNotEquals(request.body, None)
831 self.assertEquals(request.resumable_uri, None)
832
833 http = HttpMockSequence([
834 ({'status': '200',
835 'location': 'http://upload.example.com'}, ''),
836 ({'status': '308',
837 'location': 'http://upload.example.com/2',
838 'range': '0-12'}, ''),
839 ({'status': '308',
840 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500841 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500842 ({'status': '200'}, '{"foo": "bar"}'),
843 ])
844
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400845 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500846 self.assertEquals(None, body)
847 self.assertTrue(isinstance(status, MediaUploadProgress))
848 self.assertEquals(13, status.resumable_progress)
849
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500850 # Two requests should have been made and the resumable_uri should have been
851 # updated for each one.
852 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
853
854 self.assertEquals(media_upload, request.resumable)
855 self.assertEquals(13, request.resumable_progress)
856
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400857 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500858 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500859 self.assertEquals(media_upload.size()-1, request.resumable_progress)
860 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500861
862 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400863 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500864 self.assertEquals(body, {"foo": "bar"})
865 self.assertEquals(status, None)
866
867
868 def test_resumable_media_good_upload(self):
869 """Not a multipart upload."""
870 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400871 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500872
873 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
874 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500875 self.assertEquals(media_upload, request.resumable)
876
877 self.assertEquals('image/png', request.resumable.mimetype())
878
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500879 self.assertEquals(request.body, None)
880 self.assertEquals(request.resumable_uri, None)
881
882 http = HttpMockSequence([
883 ({'status': '200',
884 'location': 'http://upload.example.com'}, ''),
885 ({'status': '308',
886 'location': 'http://upload.example.com/2',
887 'range': '0-12'}, ''),
888 ({'status': '308',
889 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500890 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500891 ({'status': '200'}, '{"foo": "bar"}'),
892 ])
893
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400894 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500895 self.assertEquals(None, body)
896 self.assertTrue(isinstance(status, MediaUploadProgress))
897 self.assertEquals(13, status.resumable_progress)
898
899 # Two requests should have been made and the resumable_uri should have been
900 # updated for each one.
901 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
902
903 self.assertEquals(media_upload, request.resumable)
904 self.assertEquals(13, request.resumable_progress)
905
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400906 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500907 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500908 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500909 self.assertEquals(request.body, None)
910
911 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400912 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500913 self.assertEquals(body, {"foo": "bar"})
914 self.assertEquals(status, None)
915
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500916 def test_resumable_media_good_upload_from_execute(self):
917 """Not a multipart upload."""
918 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400919 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500920
921 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
922 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500923 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400924 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500925 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500926
927 http = HttpMockSequence([
928 ({'status': '200',
929 'location': 'http://upload.example.com'}, ''),
930 ({'status': '308',
931 'location': 'http://upload.example.com/2',
932 'range': '0-12'}, ''),
933 ({'status': '308',
934 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500935 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500936 ({'status': '200'}, '{"foo": "bar"}'),
937 ])
938
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400939 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500940 self.assertEquals(body, {"foo": "bar"})
941
942 def test_resumable_media_fail_unknown_response_code_first_request(self):
943 """Not a multipart upload."""
944 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400945 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500946
947 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
948 request = zoo.animals().insert(media_body=media_upload, body=None)
949
950 http = HttpMockSequence([
951 ({'status': '400',
952 'location': 'http://upload.example.com'}, ''),
953 ])
954
Joe Gregoriobaf04802013-03-01 12:27:06 -0500955 try:
956 request.execute(http=http)
957 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +0900958 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -0500959 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500960
961 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
962 """Not a multipart upload."""
963 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400964 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500965
966 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
967 request = zoo.animals().insert(media_body=media_upload, body=None)
968
969 http = HttpMockSequence([
970 ({'status': '200',
971 'location': 'http://upload.example.com'}, ''),
972 ({'status': '400'}, ''),
973 ])
974
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400975 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400976 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500977
Joe Gregorio910b9b12012-06-12 09:36:30 -0400978 http = HttpMockSequence([
979 ({'status': '308',
980 'range': '0-5'}, ''),
981 ({'status': '308',
982 'range': '0-6'}, ''),
983 ])
984
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400985 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400986 self.assertEquals(status.resumable_progress, 7,
987 'Should have first checked length and then tried to PUT more.')
988 self.assertFalse(request._in_error_state)
989
990 # Put it back in an error state.
991 http = HttpMockSequence([
992 ({'status': '400'}, ''),
993 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400994 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400995 self.assertTrue(request._in_error_state)
996
997 # Pretend the last request that 400'd actually succeeded.
998 http = HttpMockSequence([
999 ({'status': '200'}, '{"foo": "bar"}'),
1000 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001001 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001002 self.assertEqual(body, {'foo': 'bar'})
1003
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001004 def test_media_io_base_stream_unlimited_chunksize_resume(self):
1005 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1006 zoo = build('zoo', 'v1', http=self.http)
1007
Pat Ferateed9affd2015-03-03 16:03:15 -08001008 # Set up a seekable stream and try to upload in single chunk.
Pat Ferate2b140222015-03-03 18:05:11 -08001009 fd = BytesIO(b'01234"56789"')
Pat Ferateed9affd2015-03-03 16:03:15 -08001010 media_upload = MediaIoBaseUpload(
1011 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001012
Pat Ferateed9affd2015-03-03 16:03:15 -08001013 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001014
Pat Ferateed9affd2015-03-03 16:03:15 -08001015 # The single chunk fails, restart at the right point.
1016 http = HttpMockSequence([
1017 ({'status': '200',
1018 'location': 'http://upload.example.com'}, ''),
1019 ({'status': '308',
1020 'location': 'http://upload.example.com/2',
1021 'range': '0-4'}, ''),
1022 ({'status': '200'}, 'echo_request_body'),
1023 ])
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001024
Pat Ferateed9affd2015-03-03 16:03:15 -08001025 body = request.execute(http=http)
1026 self.assertEqual('56789', body)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001027
1028 def test_media_io_base_stream_chunksize_resume(self):
1029 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1030 zoo = build('zoo', 'v1', http=self.http)
1031
Pat Ferateed9affd2015-03-03 16:03:15 -08001032 # Set up a seekable stream and try to upload in chunks.
Pat Ferate2b140222015-03-03 18:05:11 -08001033 fd = BytesIO(b'0123456789')
Pat Ferateed9affd2015-03-03 16:03:15 -08001034 media_upload = MediaIoBaseUpload(
1035 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
1036
1037 request = zoo.animals().insert(media_body=media_upload, body=None)
1038
1039 # The single chunk fails, pull the content sent out of the exception.
1040 http = HttpMockSequence([
1041 ({'status': '200',
1042 'location': 'http://upload.example.com'}, ''),
1043 ({'status': '400'}, 'echo_request_body'),
1044 ])
1045
Joe Gregorio5c120db2012-08-23 09:13:55 -04001046 try:
Pat Ferateed9affd2015-03-03 16:03:15 -08001047 body = request.execute(http=http)
1048 except HttpError as e:
Pat Ferate2b140222015-03-03 18:05:11 -08001049 self.assertEqual(b'01234', e.content)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001050
Joe Gregorio910b9b12012-06-12 09:36:30 -04001051 def test_resumable_media_handle_uploads_of_unknown_size(self):
1052 http = HttpMockSequence([
1053 ({'status': '200',
1054 'location': 'http://upload.example.com'}, ''),
1055 ({'status': '200'}, 'echo_request_headers_as_json'),
1056 ])
1057
1058 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001059 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001060
Joe Gregorio910b9b12012-06-12 09:36:30 -04001061 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001062 class IoBaseUnknownLength(MediaUpload):
1063 def chunksize(self):
1064 return 10
1065
1066 def mimetype(self):
1067 return 'image/png'
1068
1069 def size(self):
1070 return None
1071
1072 def resumable(self):
1073 return True
1074
1075 def getbytes(self, begin, length):
1076 return '0123456789'
1077
1078 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -04001079
1080 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001081 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001082 self.assertEqual(body, {
1083 'Content-Range': 'bytes 0-9/*',
1084 'Content-Length': '10',
1085 })
Joe Gregorio44454e42012-06-15 08:38:53 -04001086
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001087 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
1088 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
1089 zoo = build('zoo', 'v1', http=self.http)
1090
1091 class IoBaseHasStream(MediaUpload):
1092 def chunksize(self):
1093 return 10
1094
1095 def mimetype(self):
1096 return 'image/png'
1097
1098 def size(self):
1099 return None
1100
1101 def resumable(self):
1102 return True
1103
1104 def getbytes(self, begin, length):
1105 return '0123456789'
1106
1107 def has_stream(self):
1108 return True
1109
1110 def stream(self):
1111 raise NotImplementedError()
1112
1113 upload = IoBaseHasStream()
1114
1115 orig_version = sys.version_info
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001116
1117 sys.version_info = (2, 6, 5, 'final', 0)
1118
1119 request = zoo.animals().insert(media_body=upload, body=None)
1120
1121 # This should raise an exception because stream() will be called.
1122 http = HttpMockSequence([
1123 ({'status': '200',
1124 'location': 'http://upload.example.com'}, ''),
1125 ({'status': '200'}, 'echo_request_headers_as_json'),
1126 ])
1127
1128 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
1129
1130 sys.version_info = orig_version
1131
Joe Gregorio44454e42012-06-15 08:38:53 -04001132 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1133 http = HttpMockSequence([
1134 ({'status': '200',
1135 'location': 'http://upload.example.com'}, ''),
1136 ({'status': '200'}, 'echo_request_headers_as_json'),
1137 ])
1138
1139 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001140 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001141
Pat Ferate2b140222015-03-03 18:05:11 -08001142 fd = BytesIO(b'data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001143
1144 # Create an upload that doesn't know the full size of the media.
1145 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001146 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001147
1148 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001149 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001150 self.assertEqual(body, {
1151 'Content-Range': 'bytes 0-13/14',
1152 'Content-Length': '14',
1153 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001154
1155 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1156 http = HttpMockSequence([
1157 ({'status': '200',
1158 'location': 'http://upload.example.com'}, ''),
1159 ({'status': '400'}, ''),
1160 ])
1161
1162 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001163 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001164
1165 # Create an upload that doesn't know the full size of the media.
Pat Ferate2b140222015-03-03 18:05:11 -08001166 fd = BytesIO(b'data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001167
1168 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001169 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001170
1171 request = zoo.animals().insert(media_body=upload, body=None)
1172
1173 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001174 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001175
1176 http = HttpMockSequence([
1177 ({'status': '400',
1178 'range': '0-5'}, 'echo_request_headers_as_json'),
1179 ])
1180 try:
1181 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001182 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001183 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001184 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001185 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001186 'content-length': '0'
1187 }
INADA Naoki09157612015-03-25 01:51:03 +09001188 self.assertEqual(expected, json.loads(e.content.decode('utf-8')),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001189 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001190
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001191 def test_pickle(self):
1192 sorted_resource_keys = ['_baseUrl',
1193 '_developerKey',
1194 '_dynamic_attrs',
1195 '_http',
1196 '_model',
1197 '_requestBuilder',
1198 '_resourceDesc',
1199 '_rootDesc',
1200 '_schema',
1201 'animals',
1202 'global_',
1203 'load',
1204 'loadNoTemplate',
1205 'my',
Pepper Lebeck-Jobe860836f2015-06-12 20:42:23 -04001206 'new_batch_http_request',
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001207 'query',
1208 'scopedAnimals']
1209
1210 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1211 zoo = build('zoo', 'v1', http=http)
1212 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1213
1214 pickled_zoo = pickle.dumps(zoo)
1215 new_zoo = pickle.loads(pickled_zoo)
1216 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1217 self.assertTrue(hasattr(new_zoo, 'animals'))
1218 self.assertTrue(callable(new_zoo.animals))
1219 self.assertTrue(hasattr(new_zoo, 'global_'))
1220 self.assertTrue(callable(new_zoo.global_))
1221 self.assertTrue(hasattr(new_zoo, 'load'))
1222 self.assertTrue(callable(new_zoo.load))
1223 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1224 self.assertTrue(callable(new_zoo.loadNoTemplate))
1225 self.assertTrue(hasattr(new_zoo, 'my'))
1226 self.assertTrue(callable(new_zoo.my))
1227 self.assertTrue(hasattr(new_zoo, 'query'))
1228 self.assertTrue(callable(new_zoo.query))
1229 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1230 self.assertTrue(callable(new_zoo.scopedAnimals))
1231
Joe Gregorio003b6e42013-02-13 15:42:19 -05001232 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001233 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1234 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1235 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1236 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1237 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1238 # _http, _model and _schema won't be equal since we will get new
1239 # instances upon un-pickling
1240
1241 def _dummy_zoo_request(self):
1242 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1243 zoo_contents = fh.read()
1244
1245 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1246 {'api': 'zoo', 'apiVersion': 'v1'})
1247 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001248 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1249 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001250
1251 http = httplib2.Http()
1252 original_request = http.request
1253 def wrapped_request(uri, method='GET', *args, **kwargs):
1254 if uri == zoo_uri:
1255 return httplib2.Response({'status': '200'}), zoo_contents
1256 return original_request(uri, method=method, *args, **kwargs)
1257 http.request = wrapped_request
1258 return http
1259
1260 def _dummy_token(self):
1261 access_token = 'foo'
1262 client_id = 'some_client_id'
1263 client_secret = 'cOuDdkfjxxnv+'
1264 refresh_token = '1/0/a.df219fjls0'
1265 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001266 user_agent = 'refresh_checker/1.0'
1267 return OAuth2Credentials(
1268 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001269 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001270 user_agent)
1271
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001272 def test_pickle_with_credentials(self):
1273 credentials = self._dummy_token()
1274 http = self._dummy_zoo_request()
1275 http = credentials.authorize(http)
1276 self.assertTrue(hasattr(http.request, 'credentials'))
1277
1278 zoo = build('zoo', 'v1', http=http)
1279 pickled_zoo = pickle.dumps(zoo)
1280 new_zoo = pickle.loads(pickled_zoo)
1281 self.assertEqual(sorted(zoo.__dict__.keys()),
1282 sorted(new_zoo.__dict__.keys()))
1283 new_http = new_zoo._http
1284 self.assertFalse(hasattr(new_http.request, 'credentials'))
1285
Joe Gregorio708388c2012-06-15 13:43:04 -04001286
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001287class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001288
Joe Gregorio3c676f92011-07-25 10:38:14 -04001289 def test_next_successful_none_on_no_next_page_token(self):
1290 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001291 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001292 request = tasks.tasklists().list()
1293 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1294
Son Dinh2a9a2132015-07-23 16:30:56 +00001295 def test_next_successful_none_on_empty_page_token(self):
1296 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
1297 tasks = build('tasks', 'v1', http=self.http)
1298 request = tasks.tasklists().list()
1299 next_request = tasks.tasklists().list_next(
1300 request, {'nextPageToken': ''})
1301 self.assertEqual(None, next_request)
1302
Joe Gregorio3c676f92011-07-25 10:38:14 -04001303 def test_next_successful_with_next_page_token(self):
1304 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001305 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001306 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001307 next_request = tasks.tasklists().list_next(
1308 request, {'nextPageToken': '123abc'})
Pat Ferated5b61bd2015-03-03 16:04:11 -08001309 parsed = list(urlparse(next_request.uri))
Joe Gregorio3c676f92011-07-25 10:38:14 -04001310 q = parse_qs(parsed[4])
1311 self.assertEqual(q['pageToken'][0], '123abc')
1312
Joe Gregorio555f33c2011-08-19 14:56:07 -04001313 def test_next_with_method_with_no_properties(self):
1314 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001315 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001316 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001317
Joe Gregorioa98733f2011-09-16 10:12:28 -04001318
Joe Gregorio708388c2012-06-15 13:43:04 -04001319class MediaGet(unittest.TestCase):
1320
1321 def test_get_media(self):
1322 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001323 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001324 request = zoo.animals().get_media(name='Lion')
1325
Pat Ferated5b61bd2015-03-03 16:04:11 -08001326 parsed = urlparse(request.uri)
Joe Gregorio708388c2012-06-15 13:43:04 -04001327 q = parse_qs(parsed[4])
1328 self.assertEqual(q['alt'], ['media'])
1329 self.assertEqual(request.headers['accept'], '*/*')
1330
1331 http = HttpMockSequence([
1332 ({'status': '200'}, 'standing in for media'),
1333 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001334 response = request.execute(http=http)
INADA Naoki09157612015-03-25 01:51:03 +09001335 self.assertEqual(b'standing in for media', response)
Joe Gregorio708388c2012-06-15 13:43:04 -04001336
1337
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001338if __name__ == '__main__':
1339 unittest.main()