blob: 70bd248ef563f6ab3511322abd5371a420f6649f [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
29
Daniel Hermesc2113242013-02-27 10:16:13 -080030import copy
Joe Gregoriodc106fc2012-11-20 14:30:14 -050031import datetime
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040032import httplib2
Craig Citro7ee535d2015-02-23 10:11:14 -080033import itertools
Craig Citro6ae34d72014-08-18 23:10:09 -070034import json
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040035import os
Joe Gregoriodc106fc2012-11-20 14:30:14 -050036import pickle
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040037import sys
Pat Ferate497a90f2015-03-09 09:52:54 -070038import unittest2 as unittest
Joe Gregorio00cf1d92010-09-27 09:22:03 -040039import urlparse
Joe Gregorio910b9b12012-06-12 09:36:30 -040040
Joe Gregorioa98733f2011-09-16 10:12:28 -040041
ade@google.comc5eb46f2010-09-27 23:35:39 +010042try:
Daniel Hermesc2113242013-02-27 10:16:13 -080043 from urlparse import parse_qs
ade@google.comc5eb46f2010-09-27 23:35:39 +010044except ImportError:
Daniel Hermesc2113242013-02-27 10:16:13 -080045 from cgi import parse_qs
Joe Gregorio00cf1d92010-09-27 09:22:03 -040046
Joe Gregoriodc106fc2012-11-20 14:30:14 -050047
John Asmuth864311d2014-04-24 15:46:08 -040048from googleapiclient.discovery import _fix_up_media_upload
49from googleapiclient.discovery import _fix_up_method_description
50from googleapiclient.discovery import _fix_up_parameters
Craig Citro7ee535d2015-02-23 10:11:14 -080051from googleapiclient.discovery import _urljoin
John Asmuth864311d2014-04-24 15:46:08 -040052from googleapiclient.discovery import build
53from googleapiclient.discovery import build_from_document
54from googleapiclient.discovery import DISCOVERY_URI
55from googleapiclient.discovery import key2param
56from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
57from googleapiclient.discovery import ResourceMethodParameters
58from googleapiclient.discovery import STACK_QUERY_PARAMETERS
59from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
60from googleapiclient.errors import HttpError
61from googleapiclient.errors import InvalidJsonError
62from googleapiclient.errors import MediaUploadSizeError
63from googleapiclient.errors import ResumableUploadError
64from googleapiclient.errors import UnacceptableMimeTypeError
65from 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."""
86 expected = urlparse.urlparse(expected)
87 actual = urlparse.urlparse(actual)
88 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:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400347 plus = build('plus', 'v1', http=self.http)
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
352
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100353class DiscoveryFromDocument(unittest.TestCase):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400354
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100355 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400356 discovery = open(datafile('plus.json')).read()
Joe Gregorio7b70f432011-11-09 10:18:51 -0500357 plus = build_from_document(discovery, base="https://www.googleapis.com/")
358 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500359 self.assertTrue(hasattr(plus, 'activities'))
360
361 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400362 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700363 discovery = json.loads(discovery)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500364 plus = build_from_document(discovery, base="https://www.googleapis.com/")
365 self.assertTrue(plus is not None)
366 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400367
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100368 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400369 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400370
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100371 base = "https://www.example.com/"
Joe Gregorio7b70f432011-11-09 10:18:51 -0500372 plus = build_from_document(discovery, base=base)
Joe Gregorioa2838152012-07-16 11:52:17 -0400373 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100374
375
Joe Gregorioa98733f2011-09-16 10:12:28 -0400376class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400377 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700378 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400379
Joe Gregorio583d9e42011-09-16 15:54:15 -0400380 def tearDown(self):
381 os.environ = self.old_environ
382
383 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400384 # build() will raise an HttpError on a 400, use this to pick the request uri
385 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400386 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400387 try:
388 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400389 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400390 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400391 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400392 discoveryServiceUrl='http://example.com')
393 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900394 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400395 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400396
Joe Gregorio583d9e42011-09-16 15:54:15 -0400397 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400398 # build() will raise an HttpError on a 400, use this to pick the request uri
399 # out of the raised exception.
400 try:
401 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400402 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400403 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400404 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400405 discoveryServiceUrl='http://example.com')
406 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900407 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400408 self.assertEqual(e.uri, 'http://example.com')
409
410
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400411class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400412
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400413 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500414 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400415 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400416
417 # Missing required parameters
418 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500419 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400420 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900421 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400422 self.assertTrue('Missing' in str(e))
423
Joe Gregorio2467afa2012-06-20 12:21:25 -0400424 # Missing required parameters even if supplied as None.
425 try:
426 plus.activities().list(collection=None, userId=None)
427 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900428 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400429 self.assertTrue('Missing' in str(e))
430
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400431 # Parameter doesn't match regex
432 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500433 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400434 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900435 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500436 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400437
438 # Unexpected parameter
439 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500440 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400441 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900442 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400443 self.assertTrue('unexpected' in str(e))
444
Joe Gregoriobee86832011-02-22 10:00:19 -0500445 def _check_query_types(self, request):
446 parsed = urlparse.urlparse(request.uri)
447 q = parse_qs(parsed[4])
448 self.assertEqual(q['q'], ['foo'])
449 self.assertEqual(q['i'], ['1'])
450 self.assertEqual(q['n'], ['1.0'])
451 self.assertEqual(q['b'], ['false'])
452 self.assertEqual(q['a'], ['[1, 2, 3]'])
453 self.assertEqual(q['o'], ['{\'a\': 1}'])
454 self.assertEqual(q['e'], ['bar'])
455
456 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400457 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400458 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500459
Joe Gregorioa98733f2011-09-16 10:12:28 -0400460 request = zoo.query(
461 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 -0500462 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400463 request = zoo.query(
464 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500465 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500466
Joe Gregorioa98733f2011-09-16 10:12:28 -0400467 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800468 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 -0500469
470 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800471 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
472 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500473 self._check_query_types(request)
474
Craig Citro1e742822012-03-01 12:59:22 -0800475 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500476 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800477
Joe Gregorio13217952011-02-22 15:37:38 -0500478 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400479 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400480 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400481 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500482
Joe Gregorioca876e42011-02-22 19:39:42 -0500483 parsed = urlparse.urlparse(request.uri)
484 q = parse_qs(parsed[4])
485 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400486 self.assertEqual(q['fields'], ['description'])
487
Joe Gregorio2467afa2012-06-20 12:21:25 -0400488 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400489 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400490 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400491 request = zoo.query(trace=None, fields='description')
492
493 parsed = urlparse.urlparse(request.uri)
494 q = parse_qs(parsed[4])
495 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400496
Joe Gregorioe08a1662011-12-07 09:48:22 -0500497 def test_model_added_query_parameters(self):
498 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400499 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500500 request = zoo.animals().get(name='Lion')
501
502 parsed = urlparse.urlparse(request.uri)
503 q = parse_qs(parsed[4])
504 self.assertEqual(q['alt'], ['json'])
505 self.assertEqual(request.headers['accept'], 'application/json')
506
507 def test_fallback_to_raw_model(self):
508 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400509 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500510 request = zoo.animals().getmedia(name='Lion')
511
512 parsed = urlparse.urlparse(request.uri)
513 q = parse_qs(parsed[4])
514 self.assertTrue('alt' not in q)
515 self.assertEqual(request.headers['accept'], '*/*')
516
Joe Gregoriof4153422011-03-18 22:45:18 -0400517 def test_patch(self):
518 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400519 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400520 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
521
522 self.assertEqual(request.method, 'PATCH')
523
524 def test_tunnel_patch(self):
525 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400526 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400527 ({'status': '200'}, 'echo_request_headers_as_json'),
528 ])
529 http = tunnel_patch(http)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400530 zoo = build('zoo', 'v1', http=http)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400531 resp = zoo.animals().patch(
532 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400533
534 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500535
Joe Gregorio7b70f432011-11-09 10:18:51 -0500536 def test_plus_resources(self):
537 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400538 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500539 self.assertTrue(getattr(plus, 'activities'))
540 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400541
Orest Bolohane92c9002014-05-30 11:15:43 -0700542 def test_credentials(self):
543 class CredentialsMock:
544 def create_scoped_required(self):
545 return False
546
547 def authorize(self, http):
548 http.orest = True
549
550 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
551 build('plus', 'v1', http=self.http, credentials=None)
552 self.assertFalse(hasattr(self.http, 'orest'))
553 build('plus', 'v1', http=self.http, credentials=CredentialsMock())
554 self.assertTrue(hasattr(self.http, 'orest'))
555
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400556 def test_full_featured(self):
557 # Zoo should exercise all discovery facets
558 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500559 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400560 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400561 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500562
Joe Gregoriof4153422011-03-18 22:45:18 -0400563 request = zoo.animals().list(name='bat', projection="full")
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400564 parsed = urlparse.urlparse(request.uri)
565 q = parse_qs(parsed[4])
566 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400567 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400568
Joe Gregorio3fada332011-01-07 17:07:45 -0500569 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500570 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400571 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500572 self.assertTrue(getattr(zoo, 'animals'))
573 request = zoo.my().favorites().list(max_results="5")
574 parsed = urlparse.urlparse(request.uri)
575 q = parse_qs(parsed[4])
576 self.assertEqual(q['max-results'], ['5'])
577
Joe Gregoriod92897c2011-07-07 11:44:56 -0400578 def test_methods_with_reserved_names(self):
579 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400580 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400581 self.assertTrue(getattr(zoo, 'animals'))
582 request = zoo.global_().print_().assert_(max_results="5")
583 parsed = urlparse.urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400584 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400585
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500586 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500587 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400588 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500589 self.assertTrue(getattr(zoo, 'query'))
590 request = zoo.query(q="foo")
591 parsed = urlparse.urlparse(request.uri)
592 q = parse_qs(parsed[4])
593 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400594
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400595 def test_simple_media_uploads(self):
596 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400597 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400598 doc = getattr(zoo.animals().insert, '__doc__')
599 self.assertTrue('media_body' in doc)
600
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400601 def test_simple_media_upload_no_max_size_provided(self):
602 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400603 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400604 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
605 self.assertEquals('image/png', request.headers['content-type'])
606 self.assertEquals('PNG', request.body[1:4])
607
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400608 def test_simple_media_raise_correct_exceptions(self):
609 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400610 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400611
612 try:
613 zoo.animals().insert(media_body=datafile('smiley.png'))
614 self.fail("should throw exception if media is too large.")
615 except MediaUploadSizeError:
616 pass
617
618 try:
619 zoo.animals().insert(media_body=datafile('small.jpg'))
620 self.fail("should throw exception if mimetype is unacceptable.")
621 except UnacceptableMimeTypeError:
622 pass
623
624 def test_simple_media_good_upload(self):
625 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400626 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400627
628 request = zoo.animals().insert(media_body=datafile('small.png'))
629 self.assertEquals('image/png', request.headers['content-type'])
630 self.assertEquals('PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500631 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400632 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500633 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400634
635 def test_multipart_media_raise_correct_exceptions(self):
636 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400637 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400638
639 try:
640 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
641 self.fail("should throw exception if media is too large.")
642 except MediaUploadSizeError:
643 pass
644
645 try:
646 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
647 self.fail("should throw exception if mimetype is unacceptable.")
648 except UnacceptableMimeTypeError:
649 pass
650
651 def test_multipart_media_good_upload(self):
652 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400653 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400654
655 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400656 self.assertTrue(request.headers['content-type'].startswith(
657 'multipart/related'))
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400658 self.assertEquals('--==', request.body[0:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500659 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400660 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500661 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400662
663 def test_media_capable_method_without_media(self):
664 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400665 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400666
667 request = zoo.animals().insert(body={})
668 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400669
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500670 def test_resumable_multipart_media_good_upload(self):
671 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400672 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500673
674 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
675 request = zoo.animals().insert(media_body=media_upload, body={})
676 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500677 'application/json'))
678 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500679 self.assertEquals(media_upload, request.resumable)
680
681 self.assertEquals('image/png', request.resumable.mimetype())
682
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500683 self.assertNotEquals(request.body, None)
684 self.assertEquals(request.resumable_uri, None)
685
686 http = HttpMockSequence([
687 ({'status': '200',
688 'location': 'http://upload.example.com'}, ''),
689 ({'status': '308',
690 'location': 'http://upload.example.com/2',
691 'range': '0-12'}, ''),
692 ({'status': '308',
693 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500694 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500695 ({'status': '200'}, '{"foo": "bar"}'),
696 ])
697
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400698 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500699 self.assertEquals(None, body)
700 self.assertTrue(isinstance(status, MediaUploadProgress))
701 self.assertEquals(13, status.resumable_progress)
702
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500703 # Two requests should have been made and the resumable_uri should have been
704 # updated for each one.
705 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
706
707 self.assertEquals(media_upload, request.resumable)
708 self.assertEquals(13, request.resumable_progress)
709
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400710 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500711 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500712 self.assertEquals(media_upload.size()-1, request.resumable_progress)
713 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500714
715 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400716 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500717 self.assertEquals(body, {"foo": "bar"})
718 self.assertEquals(status, None)
719
720
721 def test_resumable_media_good_upload(self):
722 """Not a multipart upload."""
723 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400724 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500725
726 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
727 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500728 self.assertEquals(media_upload, request.resumable)
729
730 self.assertEquals('image/png', request.resumable.mimetype())
731
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500732 self.assertEquals(request.body, None)
733 self.assertEquals(request.resumable_uri, None)
734
735 http = HttpMockSequence([
736 ({'status': '200',
737 'location': 'http://upload.example.com'}, ''),
738 ({'status': '308',
739 'location': 'http://upload.example.com/2',
740 'range': '0-12'}, ''),
741 ({'status': '308',
742 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500743 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500744 ({'status': '200'}, '{"foo": "bar"}'),
745 ])
746
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400747 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500748 self.assertEquals(None, body)
749 self.assertTrue(isinstance(status, MediaUploadProgress))
750 self.assertEquals(13, status.resumable_progress)
751
752 # Two requests should have been made and the resumable_uri should have been
753 # updated for each one.
754 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
755
756 self.assertEquals(media_upload, request.resumable)
757 self.assertEquals(13, request.resumable_progress)
758
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400759 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500760 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500761 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500762 self.assertEquals(request.body, None)
763
764 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400765 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500766 self.assertEquals(body, {"foo": "bar"})
767 self.assertEquals(status, None)
768
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500769 def test_resumable_media_good_upload_from_execute(self):
770 """Not a multipart upload."""
771 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400772 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500773
774 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
775 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500776 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400777 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500778 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500779
780 http = HttpMockSequence([
781 ({'status': '200',
782 'location': 'http://upload.example.com'}, ''),
783 ({'status': '308',
784 'location': 'http://upload.example.com/2',
785 'range': '0-12'}, ''),
786 ({'status': '308',
787 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500788 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500789 ({'status': '200'}, '{"foo": "bar"}'),
790 ])
791
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400792 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500793 self.assertEquals(body, {"foo": "bar"})
794
795 def test_resumable_media_fail_unknown_response_code_first_request(self):
796 """Not a multipart upload."""
797 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400798 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500799
800 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
801 request = zoo.animals().insert(media_body=media_upload, body=None)
802
803 http = HttpMockSequence([
804 ({'status': '400',
805 'location': 'http://upload.example.com'}, ''),
806 ])
807
Joe Gregoriobaf04802013-03-01 12:27:06 -0500808 try:
809 request.execute(http=http)
810 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +0900811 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -0500812 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500813
814 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
815 """Not a multipart upload."""
816 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400817 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500818
819 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
820 request = zoo.animals().insert(media_body=media_upload, body=None)
821
822 http = HttpMockSequence([
823 ({'status': '200',
824 'location': 'http://upload.example.com'}, ''),
825 ({'status': '400'}, ''),
826 ])
827
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400828 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400829 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500830
Joe Gregorio910b9b12012-06-12 09:36:30 -0400831 http = HttpMockSequence([
832 ({'status': '308',
833 'range': '0-5'}, ''),
834 ({'status': '308',
835 'range': '0-6'}, ''),
836 ])
837
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400838 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400839 self.assertEquals(status.resumable_progress, 7,
840 'Should have first checked length and then tried to PUT more.')
841 self.assertFalse(request._in_error_state)
842
843 # Put it back in an error state.
844 http = HttpMockSequence([
845 ({'status': '400'}, ''),
846 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400847 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400848 self.assertTrue(request._in_error_state)
849
850 # Pretend the last request that 400'd actually succeeded.
851 http = HttpMockSequence([
852 ({'status': '200'}, '{"foo": "bar"}'),
853 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400854 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400855 self.assertEqual(body, {'foo': 'bar'})
856
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400857 def test_media_io_base_stream_unlimited_chunksize_resume(self):
858 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
859 zoo = build('zoo', 'v1', http=self.http)
860
Pat Ferateed9affd2015-03-03 16:03:15 -0800861 # Set up a seekable stream and try to upload in single chunk.
862 fd = BytesIO('01234"56789"')
863 media_upload = MediaIoBaseUpload(
864 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400865
Pat Ferateed9affd2015-03-03 16:03:15 -0800866 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400867
Pat Ferateed9affd2015-03-03 16:03:15 -0800868 # The single chunk fails, restart at the right point.
869 http = HttpMockSequence([
870 ({'status': '200',
871 'location': 'http://upload.example.com'}, ''),
872 ({'status': '308',
873 'location': 'http://upload.example.com/2',
874 'range': '0-4'}, ''),
875 ({'status': '200'}, 'echo_request_body'),
876 ])
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400877
Pat Ferateed9affd2015-03-03 16:03:15 -0800878 body = request.execute(http=http)
879 self.assertEqual('56789', body)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400880
881 def test_media_io_base_stream_chunksize_resume(self):
882 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
883 zoo = build('zoo', 'v1', http=self.http)
884
Pat Ferateed9affd2015-03-03 16:03:15 -0800885 # Set up a seekable stream and try to upload in chunks.
886 fd = BytesIO('0123456789')
887 media_upload = MediaIoBaseUpload(
888 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
889
890 request = zoo.animals().insert(media_body=media_upload, body=None)
891
892 # The single chunk fails, pull the content sent out of the exception.
893 http = HttpMockSequence([
894 ({'status': '200',
895 'location': 'http://upload.example.com'}, ''),
896 ({'status': '400'}, 'echo_request_body'),
897 ])
898
Joe Gregorio5c120db2012-08-23 09:13:55 -0400899 try:
Pat Ferateed9affd2015-03-03 16:03:15 -0800900 body = request.execute(http=http)
901 except HttpError as e:
902 self.assertEqual('01234', e.content)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400903
Joe Gregorio910b9b12012-06-12 09:36:30 -0400904 def test_resumable_media_handle_uploads_of_unknown_size(self):
905 http = HttpMockSequence([
906 ({'status': '200',
907 'location': 'http://upload.example.com'}, ''),
908 ({'status': '200'}, 'echo_request_headers_as_json'),
909 ])
910
911 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400912 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400913
Joe Gregorio910b9b12012-06-12 09:36:30 -0400914 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400915 class IoBaseUnknownLength(MediaUpload):
916 def chunksize(self):
917 return 10
918
919 def mimetype(self):
920 return 'image/png'
921
922 def size(self):
923 return None
924
925 def resumable(self):
926 return True
927
928 def getbytes(self, begin, length):
929 return '0123456789'
930
931 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400932
933 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400934 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400935 self.assertEqual(body, {
936 'Content-Range': 'bytes 0-9/*',
937 'Content-Length': '10',
938 })
Joe Gregorio44454e42012-06-15 08:38:53 -0400939
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400940 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
941 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
942 zoo = build('zoo', 'v1', http=self.http)
943
944 class IoBaseHasStream(MediaUpload):
945 def chunksize(self):
946 return 10
947
948 def mimetype(self):
949 return 'image/png'
950
951 def size(self):
952 return None
953
954 def resumable(self):
955 return True
956
957 def getbytes(self, begin, length):
958 return '0123456789'
959
960 def has_stream(self):
961 return True
962
963 def stream(self):
964 raise NotImplementedError()
965
966 upload = IoBaseHasStream()
967
968 orig_version = sys.version_info
969 sys.version_info = (2, 5, 5, 'final', 0)
970
971 request = zoo.animals().insert(media_body=upload, body=None)
972
973 http = HttpMockSequence([
974 ({'status': '200',
975 'location': 'http://upload.example.com'}, ''),
976 ({'status': '200'}, 'echo_request_headers_as_json'),
977 ])
978
979 # This should not raise an exception because stream() shouldn't be called.
980 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400981 self.assertEqual(body, {
982 'Content-Range': 'bytes 0-9/*',
983 'Content-Length': '10'
984 })
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400985
986 sys.version_info = (2, 6, 5, 'final', 0)
987
988 request = zoo.animals().insert(media_body=upload, body=None)
989
990 # This should raise an exception because stream() will be called.
991 http = HttpMockSequence([
992 ({'status': '200',
993 'location': 'http://upload.example.com'}, ''),
994 ({'status': '200'}, 'echo_request_headers_as_json'),
995 ])
996
997 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
998
999 sys.version_info = orig_version
1000
Joe Gregorio44454e42012-06-15 08:38:53 -04001001 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1002 http = HttpMockSequence([
1003 ({'status': '200',
1004 'location': 'http://upload.example.com'}, ''),
1005 ({'status': '200'}, 'echo_request_headers_as_json'),
1006 ])
1007
1008 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001009 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001010
Pat Ferateed9affd2015-03-03 16:03:15 -08001011 fd = BytesIO('data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001012
1013 # Create an upload that doesn't know the full size of the media.
1014 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001015 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001016
1017 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001018 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001019 self.assertEqual(body, {
1020 'Content-Range': 'bytes 0-13/14',
1021 'Content-Length': '14',
1022 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001023
1024 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1025 http = HttpMockSequence([
1026 ({'status': '200',
1027 'location': 'http://upload.example.com'}, ''),
1028 ({'status': '400'}, ''),
1029 ])
1030
1031 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001032 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001033
1034 # Create an upload that doesn't know the full size of the media.
Pat Ferateed9affd2015-03-03 16:03:15 -08001035 fd = BytesIO('data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001036
1037 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001038 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001039
1040 request = zoo.animals().insert(media_body=upload, body=None)
1041
1042 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001043 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001044
1045 http = HttpMockSequence([
1046 ({'status': '400',
1047 'range': '0-5'}, 'echo_request_headers_as_json'),
1048 ])
1049 try:
1050 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001051 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001052 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001053 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001054 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001055 'content-length': '0'
1056 }
Craig Citro6ae34d72014-08-18 23:10:09 -07001057 self.assertEqual(expected, json.loads(e.content),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001058 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001059
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001060 def test_pickle(self):
1061 sorted_resource_keys = ['_baseUrl',
1062 '_developerKey',
1063 '_dynamic_attrs',
1064 '_http',
1065 '_model',
1066 '_requestBuilder',
1067 '_resourceDesc',
1068 '_rootDesc',
1069 '_schema',
1070 'animals',
1071 'global_',
1072 'load',
1073 'loadNoTemplate',
1074 'my',
1075 'query',
1076 'scopedAnimals']
1077
1078 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1079 zoo = build('zoo', 'v1', http=http)
1080 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1081
1082 pickled_zoo = pickle.dumps(zoo)
1083 new_zoo = pickle.loads(pickled_zoo)
1084 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1085 self.assertTrue(hasattr(new_zoo, 'animals'))
1086 self.assertTrue(callable(new_zoo.animals))
1087 self.assertTrue(hasattr(new_zoo, 'global_'))
1088 self.assertTrue(callable(new_zoo.global_))
1089 self.assertTrue(hasattr(new_zoo, 'load'))
1090 self.assertTrue(callable(new_zoo.load))
1091 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1092 self.assertTrue(callable(new_zoo.loadNoTemplate))
1093 self.assertTrue(hasattr(new_zoo, 'my'))
1094 self.assertTrue(callable(new_zoo.my))
1095 self.assertTrue(hasattr(new_zoo, 'query'))
1096 self.assertTrue(callable(new_zoo.query))
1097 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1098 self.assertTrue(callable(new_zoo.scopedAnimals))
1099
Joe Gregorio003b6e42013-02-13 15:42:19 -05001100 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001101 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1102 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1103 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1104 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1105 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1106 # _http, _model and _schema won't be equal since we will get new
1107 # instances upon un-pickling
1108
1109 def _dummy_zoo_request(self):
1110 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1111 zoo_contents = fh.read()
1112
1113 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1114 {'api': 'zoo', 'apiVersion': 'v1'})
1115 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001116 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1117 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001118
1119 http = httplib2.Http()
1120 original_request = http.request
1121 def wrapped_request(uri, method='GET', *args, **kwargs):
1122 if uri == zoo_uri:
1123 return httplib2.Response({'status': '200'}), zoo_contents
1124 return original_request(uri, method=method, *args, **kwargs)
1125 http.request = wrapped_request
1126 return http
1127
1128 def _dummy_token(self):
1129 access_token = 'foo'
1130 client_id = 'some_client_id'
1131 client_secret = 'cOuDdkfjxxnv+'
1132 refresh_token = '1/0/a.df219fjls0'
1133 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001134 user_agent = 'refresh_checker/1.0'
1135 return OAuth2Credentials(
1136 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001137 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001138 user_agent)
1139
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001140 def test_pickle_with_credentials(self):
1141 credentials = self._dummy_token()
1142 http = self._dummy_zoo_request()
1143 http = credentials.authorize(http)
1144 self.assertTrue(hasattr(http.request, 'credentials'))
1145
1146 zoo = build('zoo', 'v1', http=http)
1147 pickled_zoo = pickle.dumps(zoo)
1148 new_zoo = pickle.loads(pickled_zoo)
1149 self.assertEqual(sorted(zoo.__dict__.keys()),
1150 sorted(new_zoo.__dict__.keys()))
1151 new_http = new_zoo._http
1152 self.assertFalse(hasattr(new_http.request, 'credentials'))
1153
Joe Gregorio708388c2012-06-15 13:43:04 -04001154
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001155class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001156
Joe Gregorio3c676f92011-07-25 10:38:14 -04001157 def test_next_successful_none_on_no_next_page_token(self):
1158 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001159 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001160 request = tasks.tasklists().list()
1161 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1162
1163 def test_next_successful_with_next_page_token(self):
1164 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001165 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001166 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001167 next_request = tasks.tasklists().list_next(
1168 request, {'nextPageToken': '123abc'})
Joe Gregorio3c676f92011-07-25 10:38:14 -04001169 parsed = list(urlparse.urlparse(next_request.uri))
1170 q = parse_qs(parsed[4])
1171 self.assertEqual(q['pageToken'][0], '123abc')
1172
Joe Gregorio555f33c2011-08-19 14:56:07 -04001173 def test_next_with_method_with_no_properties(self):
1174 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001175 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001176 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001177
Joe Gregorioa98733f2011-09-16 10:12:28 -04001178
Joe Gregorio708388c2012-06-15 13:43:04 -04001179class MediaGet(unittest.TestCase):
1180
1181 def test_get_media(self):
1182 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001183 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001184 request = zoo.animals().get_media(name='Lion')
1185
1186 parsed = urlparse.urlparse(request.uri)
1187 q = parse_qs(parsed[4])
1188 self.assertEqual(q['alt'], ['media'])
1189 self.assertEqual(request.headers['accept'], '*/*')
1190
1191 http = HttpMockSequence([
1192 ({'status': '200'}, 'standing in for media'),
1193 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001194 response = request.execute(http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001195 self.assertEqual('standing in for media', response)
1196
1197
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001198if __name__ == '__main__':
1199 unittest.main()