blob: ccc69c2b05cac73976ff46b8c7060603c8f63f9c [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
Daniel Hermesc2113242013-02-27 10:16:13 -080028import copy
Joe Gregoriodc106fc2012-11-20 14:30:14 -050029import datetime
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040030import httplib2
Craig Citro7ee535d2015-02-23 10:11:14 -080031import itertools
Craig Citro6ae34d72014-08-18 23:10:09 -070032import json
Joe Gregorioba9ea7f2010-08-19 15:49:04 -040033import os
Joe Gregoriodc106fc2012-11-20 14:30:14 -050034import pickle
Joe Gregorioc80ac9d2012-08-21 14:09:09 -040035import sys
Pat Ferate497a90f2015-03-09 09:52:54 -070036import unittest2 as unittest
Joe Gregorio00cf1d92010-09-27 09:22:03 -040037import urlparse
Joe Gregorio910b9b12012-06-12 09:36:30 -040038import StringIO
39
Joe Gregorioa98733f2011-09-16 10:12:28 -040040
ade@google.comc5eb46f2010-09-27 23:35:39 +010041try:
Daniel Hermesc2113242013-02-27 10:16:13 -080042 from urlparse import parse_qs
ade@google.comc5eb46f2010-09-27 23:35:39 +010043except ImportError:
Daniel Hermesc2113242013-02-27 10:16:13 -080044 from cgi import parse_qs
Joe Gregorio00cf1d92010-09-27 09:22:03 -040045
Joe Gregoriodc106fc2012-11-20 14:30:14 -050046
John Asmuth864311d2014-04-24 15:46:08 -040047from googleapiclient.discovery import _fix_up_media_upload
48from googleapiclient.discovery import _fix_up_method_description
49from googleapiclient.discovery import _fix_up_parameters
Craig Citro7ee535d2015-02-23 10:11:14 -080050from googleapiclient.discovery import _urljoin
John Asmuth864311d2014-04-24 15:46:08 -040051from googleapiclient.discovery import build
52from googleapiclient.discovery import build_from_document
53from googleapiclient.discovery import DISCOVERY_URI
54from googleapiclient.discovery import key2param
55from googleapiclient.discovery import MEDIA_BODY_PARAMETER_DEFAULT_VALUE
56from googleapiclient.discovery import ResourceMethodParameters
57from googleapiclient.discovery import STACK_QUERY_PARAMETERS
58from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE
59from googleapiclient.errors import HttpError
60from googleapiclient.errors import InvalidJsonError
61from googleapiclient.errors import MediaUploadSizeError
62from googleapiclient.errors import ResumableUploadError
63from googleapiclient.errors import UnacceptableMimeTypeError
64from googleapiclient.http import HttpMock
65from googleapiclient.http import HttpMockSequence
66from googleapiclient.http import MediaFileUpload
67from googleapiclient.http import MediaIoBaseUpload
68from googleapiclient.http import MediaUpload
69from googleapiclient.http import MediaUploadProgress
70from googleapiclient.http import tunnel_patch
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080071from oauth2client import GOOGLE_TOKEN_URI
Joe Gregorio79daca02013-03-29 16:25:52 -040072from oauth2client import util
Joe Gregoriodc106fc2012-11-20 14:30:14 -050073from oauth2client.client import OAuth2Credentials
Joe Gregorio79daca02013-03-29 16:25:52 -040074
Joe Gregoriodc106fc2012-11-20 14:30:14 -050075import uritemplate
76
Joe Gregoriocb8103d2011-02-11 23:20:52 -050077
78DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
79
Joe Gregorio79daca02013-03-29 16:25:52 -040080util.positional_parameters_enforcement = util.POSITIONAL_EXCEPTION
Joe Gregorio32f048f2012-08-27 16:31:27 -040081
Joe Gregorioa98733f2011-09-16 10:12:28 -040082
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050083def assertUrisEqual(testcase, expected, actual):
84 """Test that URIs are the same, up to reordering of query parameters."""
85 expected = urlparse.urlparse(expected)
86 actual = urlparse.urlparse(actual)
87 testcase.assertEqual(expected.scheme, actual.scheme)
88 testcase.assertEqual(expected.netloc, actual.netloc)
89 testcase.assertEqual(expected.path, actual.path)
90 testcase.assertEqual(expected.params, actual.params)
91 testcase.assertEqual(expected.fragment, actual.fragment)
92 expected_query = parse_qs(expected.query)
93 actual_query = parse_qs(actual.query)
INADA Naokid898a372015-03-04 03:52:46 +090094 for name in list(expected_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050095 testcase.assertEqual(expected_query[name], actual_query[name])
INADA Naokid898a372015-03-04 03:52:46 +090096 for name in list(actual_query.keys()):
Joe Gregoriof1ba7f12012-11-16 15:10:47 -050097 testcase.assertEqual(expected_query[name], actual_query[name])
98
99
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500100def datafile(filename):
101 return os.path.join(DATA_DIR, filename)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400102
103
Joe Gregorio504a17f2012-12-07 14:14:26 -0500104class SetupHttplib2(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800105
Joe Gregorio504a17f2012-12-07 14:14:26 -0500106 def test_retries(self):
John Asmuth864311d2014-04-24 15:46:08 -0400107 # Merely loading googleapiclient.discovery should set the RETRIES to 1.
Joe Gregorio504a17f2012-12-07 14:14:26 -0500108 self.assertEqual(1, httplib2.RETRIES)
109
110
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400111class Utilities(unittest.TestCase):
Daniel Hermesc2113242013-02-27 10:16:13 -0800112
113 def setUp(self):
114 with open(datafile('zoo.json'), 'r') as fh:
Craig Citro6ae34d72014-08-18 23:10:09 -0700115 self.zoo_root_desc = json.loads(fh.read())
Daniel Hermesc2113242013-02-27 10:16:13 -0800116 self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
Daniel Hermes954e1242013-02-28 09:28:37 -0800117 self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
118 self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
Daniel Hermesc2113242013-02-27 10:16:13 -0800119
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400120 def test_key2param(self):
121 self.assertEqual('max_results', key2param('max-results'))
122 self.assertEqual('x007_bond', key2param('007-bond'))
123
Daniel Hermesc2113242013-02-27 10:16:13 -0800124 def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
125 self.assertEqual(method_desc['httpMethod'], http_method)
126
127 method_desc_copy = copy.deepcopy(method_desc)
128 self.assertEqual(method_desc, method_desc_copy)
129
130 parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
131
132 self.assertNotEqual(method_desc, method_desc_copy)
133
134 for param_name in STACK_QUERY_PARAMETERS:
135 self.assertEqual(STACK_QUERY_PARAMETER_DEFAULT_VALUE,
136 parameters[param_name])
137
INADA Naokid898a372015-03-04 03:52:46 +0900138 for param_name, value in six.iteritems(root_desc.get('parameters', {})):
Daniel Hermesc2113242013-02-27 10:16:13 -0800139 self.assertEqual(value, parameters[param_name])
140
141 return parameters
142
143 def test_fix_up_parameters_get(self):
144 parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
145 'GET', self.zoo_root_desc)
146 # Since http_method is 'GET'
INADA Naoki0bceb332014-08-20 15:27:52 +0900147 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800148
149 def test_fix_up_parameters_insert(self):
150 parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
151 'POST', self.zoo_root_desc)
152 body = {
153 'description': 'The request body.',
154 'type': 'object',
155 'required': True,
156 '$ref': 'Animal',
157 }
158 self.assertEqual(parameters['body'], body)
159
160 def test_fix_up_parameters_check_body(self):
161 dummy_root_desc = {}
162 no_payload_http_method = 'DELETE'
163 with_payload_http_method = 'PUT'
164
165 invalid_method_desc = {'response': 'Who cares'}
166 valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
167
168 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
169 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900170 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800171
172 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
173 no_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900174 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800175
176 parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
177 with_payload_http_method)
INADA Naoki0bceb332014-08-20 15:27:52 +0900178 self.assertFalse('body' in parameters)
Daniel Hermesc2113242013-02-27 10:16:13 -0800179
180 parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
181 with_payload_http_method)
182 body = {
183 'description': 'The request body.',
184 'type': 'object',
185 'required': True,
186 'key1': 'value1',
187 'key2': 'value2',
188 }
189 self.assertEqual(parameters['body'], body)
190
191 def _base_fix_up_method_description_test(
192 self, method_desc, initial_parameters, final_parameters,
193 final_accept, final_max_size, final_media_path_url):
194 fake_root_desc = {'rootUrl': 'http://root/',
195 'servicePath': 'fake/'}
196 fake_path_url = 'fake-path/'
197
198 accept, max_size, media_path_url = _fix_up_media_upload(
199 method_desc, fake_root_desc, fake_path_url, initial_parameters)
200 self.assertEqual(accept, final_accept)
201 self.assertEqual(max_size, final_max_size)
202 self.assertEqual(media_path_url, final_media_path_url)
203 self.assertEqual(initial_parameters, final_parameters)
204
205 def test_fix_up_media_upload_no_initial_invalid(self):
206 invalid_method_desc = {'response': 'Who cares'}
207 self._base_fix_up_method_description_test(invalid_method_desc, {}, {},
208 [], 0, None)
209
210 def test_fix_up_media_upload_no_initial_valid_minimal(self):
211 valid_method_desc = {'mediaUpload': {'accept': []}}
212 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
213 self._base_fix_up_method_description_test(
214 valid_method_desc, {}, final_parameters, [], 0,
215 'http://root/upload/fake/fake-path/')
216
217 def test_fix_up_media_upload_no_initial_valid_full(self):
218 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
219 final_parameters = {'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
220 ten_gb = 10 * 2**30
221 self._base_fix_up_method_description_test(
222 valid_method_desc, {}, final_parameters, ['*/*'],
223 ten_gb, 'http://root/upload/fake/fake-path/')
224
225 def test_fix_up_media_upload_with_initial_invalid(self):
226 invalid_method_desc = {'response': 'Who cares'}
227 initial_parameters = {'body': {}}
228 self._base_fix_up_method_description_test(
229 invalid_method_desc, initial_parameters,
230 initial_parameters, [], 0, None)
231
232 def test_fix_up_media_upload_with_initial_valid_minimal(self):
233 valid_method_desc = {'mediaUpload': {'accept': []}}
234 initial_parameters = {'body': {}}
235 final_parameters = {'body': {'required': False},
236 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
237 self._base_fix_up_method_description_test(
238 valid_method_desc, initial_parameters, final_parameters, [], 0,
239 'http://root/upload/fake/fake-path/')
240
241 def test_fix_up_media_upload_with_initial_valid_full(self):
242 valid_method_desc = {'mediaUpload': {'accept': ['*/*'], 'maxSize': '10GB'}}
243 initial_parameters = {'body': {}}
244 final_parameters = {'body': {'required': False},
245 'media_body': MEDIA_BODY_PARAMETER_DEFAULT_VALUE}
246 ten_gb = 10 * 2**30
247 self._base_fix_up_method_description_test(
248 valid_method_desc, initial_parameters, final_parameters, ['*/*'],
249 ten_gb, 'http://root/upload/fake/fake-path/')
250
251 def test_fix_up_method_description_get(self):
252 result = _fix_up_method_description(self.zoo_get_method_desc,
253 self.zoo_root_desc)
254 path_url = 'query'
255 http_method = 'GET'
256 method_id = 'bigquery.query'
257 accept = []
INADA Naoki0bceb332014-08-20 15:27:52 +0900258 max_size = 0
Daniel Hermesc2113242013-02-27 10:16:13 -0800259 media_path_url = None
260 self.assertEqual(result, (path_url, http_method, method_id, accept,
261 max_size, media_path_url))
262
263 def test_fix_up_method_description_insert(self):
264 result = _fix_up_method_description(self.zoo_insert_method_desc,
265 self.zoo_root_desc)
266 path_url = 'animals'
267 http_method = 'POST'
268 method_id = 'zoo.animals.insert'
269 accept = ['image/png']
INADA Naoki0bceb332014-08-20 15:27:52 +0900270 max_size = 1024
Daniel Hermesc2113242013-02-27 10:16:13 -0800271 media_path_url = 'https://www.googleapis.com/upload/zoo/v1/animals'
272 self.assertEqual(result, (path_url, http_method, method_id, accept,
273 max_size, media_path_url))
274
Craig Citro7ee535d2015-02-23 10:11:14 -0800275 def test_urljoin(self):
276 # We want to exhaustively test various URL combinations.
277 simple_bases = ['https://www.googleapis.com', 'https://www.googleapis.com/']
278 long_urls = ['foo/v1/bar:custom?alt=json', '/foo/v1/bar:custom?alt=json']
279
280 long_bases = [
281 'https://www.googleapis.com/foo/v1',
282 'https://www.googleapis.com/foo/v1/',
283 ]
284 simple_urls = ['bar:custom?alt=json', '/bar:custom?alt=json']
285
286 final_url = 'https://www.googleapis.com/foo/v1/bar:custom?alt=json'
287 for base, url in itertools.product(simple_bases, long_urls):
288 self.assertEqual(final_url, _urljoin(base, url))
289 for base, url in itertools.product(long_bases, simple_urls):
290 self.assertEqual(final_url, _urljoin(base, url))
291
292
Daniel Hermes954e1242013-02-28 09:28:37 -0800293 def test_ResourceMethodParameters_zoo_get(self):
294 parameters = ResourceMethodParameters(self.zoo_get_method_desc)
295
296 param_types = {'a': 'any',
297 'b': 'boolean',
298 'e': 'string',
299 'er': 'string',
300 'i': 'integer',
301 'n': 'number',
302 'o': 'object',
303 'q': 'string',
304 'rr': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900305 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800306 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
307 self.assertEqual(parameters.required_params, [])
308 self.assertEqual(sorted(parameters.repeated_params), ['er', 'rr'])
309 self.assertEqual(parameters.pattern_params, {'rr': '[a-z]+'})
310 self.assertEqual(sorted(parameters.query_params),
311 ['a', 'b', 'e', 'er', 'i', 'n', 'o', 'q', 'rr'])
312 self.assertEqual(parameters.path_params, set())
313 self.assertEqual(parameters.param_types, param_types)
314 enum_params = {'e': ['foo', 'bar'],
315 'er': ['one', 'two', 'three']}
316 self.assertEqual(parameters.enum_params, enum_params)
317
318 def test_ResourceMethodParameters_zoo_animals_patch(self):
319 method_desc = self.zoo_animals_resource['methods']['patch']
320 parameters = ResourceMethodParameters(method_desc)
321
322 param_types = {'name': 'string'}
INADA Naokid898a372015-03-04 03:52:46 +0900323 keys = list(param_types.keys())
Daniel Hermes954e1242013-02-28 09:28:37 -0800324 self.assertEqual(parameters.argmap, dict((key, key) for key in keys))
325 self.assertEqual(parameters.required_params, ['name'])
326 self.assertEqual(parameters.repeated_params, [])
327 self.assertEqual(parameters.pattern_params, {})
328 self.assertEqual(parameters.query_params, [])
329 self.assertEqual(parameters.path_params, set(['name']))
330 self.assertEqual(parameters.param_types, param_types)
331 self.assertEqual(parameters.enum_params, {})
332
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400333
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500334class DiscoveryErrors(unittest.TestCase):
335
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400336 def test_tests_should_be_run_with_strict_positional_enforcement(self):
337 try:
338 plus = build('plus', 'v1', None)
339 self.fail("should have raised a TypeError exception over missing http=.")
340 except TypeError:
341 pass
342
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500343 def test_failed_to_parse_discovery_json(self):
344 self.http = HttpMock(datafile('malformed.json'), {'status': '200'})
345 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400346 plus = build('plus', 'v1', http=self.http)
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500347 self.fail("should have raised an exception over malformed JSON.")
Joe Gregorio49396552011-03-08 10:39:00 -0500348 except InvalidJsonError:
349 pass
Joe Gregorioc0e0fe92011-03-04 16:16:55 -0500350
351
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100352class DiscoveryFromDocument(unittest.TestCase):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400353
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100354 def test_can_build_from_local_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400355 discovery = open(datafile('plus.json')).read()
Joe Gregorio7b70f432011-11-09 10:18:51 -0500356 plus = build_from_document(discovery, base="https://www.googleapis.com/")
357 self.assertTrue(plus is not None)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500358 self.assertTrue(hasattr(plus, 'activities'))
359
360 def test_can_build_from_local_deserialized_document(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400361 discovery = open(datafile('plus.json')).read()
Craig Citro6ae34d72014-08-18 23:10:09 -0700362 discovery = json.loads(discovery)
Joe Gregorio4772f3d2012-12-10 10:22:37 -0500363 plus = build_from_document(discovery, base="https://www.googleapis.com/")
364 self.assertTrue(plus is not None)
365 self.assertTrue(hasattr(plus, 'activities'))
Joe Gregorioa98733f2011-09-16 10:12:28 -0400366
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100367 def test_building_with_base_remembers_base(self):
Joe Gregorio79daca02013-03-29 16:25:52 -0400368 discovery = open(datafile('plus.json')).read()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400369
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100370 base = "https://www.example.com/"
Joe Gregorio7b70f432011-11-09 10:18:51 -0500371 plus = build_from_document(discovery, base=base)
Joe Gregorioa2838152012-07-16 11:52:17 -0400372 self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
ade@google.com6a8c1cb2011-09-06 17:40:00 +0100373
374
Joe Gregorioa98733f2011-09-16 10:12:28 -0400375class DiscoveryFromHttp(unittest.TestCase):
Joe Gregorio583d9e42011-09-16 15:54:15 -0400376 def setUp(self):
Joe Bedafb463cb2011-09-19 17:39:49 -0700377 self.old_environ = os.environ.copy()
Joe Gregorioa98733f2011-09-16 10:12:28 -0400378
Joe Gregorio583d9e42011-09-16 15:54:15 -0400379 def tearDown(self):
380 os.environ = self.old_environ
381
382 def test_userip_is_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400383 # build() will raise an HttpError on a 400, use this to pick the request uri
384 # out of the raised exception.
Joe Gregorio583d9e42011-09-16 15:54:15 -0400385 os.environ['REMOTE_ADDR'] = '10.0.0.1'
Joe Gregorioa98733f2011-09-16 10:12:28 -0400386 try:
387 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400388 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400389 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400390 zoo = build('zoo', 'v1', http=http, developerKey='foo',
Joe Gregorioa98733f2011-09-16 10:12:28 -0400391 discoveryServiceUrl='http://example.com')
392 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900393 except HttpError as e:
Joe Gregorio583d9e42011-09-16 15:54:15 -0400394 self.assertEqual(e.uri, 'http://example.com?userIp=10.0.0.1')
Joe Gregorioa98733f2011-09-16 10:12:28 -0400395
Joe Gregorio583d9e42011-09-16 15:54:15 -0400396 def test_userip_missing_is_not_added_to_discovery_uri(self):
Joe Gregorioa98733f2011-09-16 10:12:28 -0400397 # build() will raise an HttpError on a 400, use this to pick the request uri
398 # out of the raised exception.
399 try:
400 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400401 ({'status': '400'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregorioa98733f2011-09-16 10:12:28 -0400402 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400403 zoo = build('zoo', 'v1', http=http, developerKey=None,
Joe Gregorioa98733f2011-09-16 10:12:28 -0400404 discoveryServiceUrl='http://example.com')
405 self.fail('Should have raised an exception.')
INADA Naokic1505df2014-08-20 15:19:53 +0900406 except HttpError as e:
Joe Gregorioa98733f2011-09-16 10:12:28 -0400407 self.assertEqual(e.uri, 'http://example.com')
408
409
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400410class Discovery(unittest.TestCase):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400411
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400412 def test_method_error_checking(self):
Joe Gregorio7b70f432011-11-09 10:18:51 -0500413 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400414 plus = build('plus', 'v1', http=self.http)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400415
416 # Missing required parameters
417 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500418 plus.activities().list()
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400419 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900420 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400421 self.assertTrue('Missing' in str(e))
422
Joe Gregorio2467afa2012-06-20 12:21:25 -0400423 # Missing required parameters even if supplied as None.
424 try:
425 plus.activities().list(collection=None, userId=None)
426 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900427 except TypeError as e:
Joe Gregorio2467afa2012-06-20 12:21:25 -0400428 self.assertTrue('Missing' in str(e))
429
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400430 # Parameter doesn't match regex
431 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500432 plus.activities().list(collection='not_a_collection_name', userId='me')
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400433 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900434 except TypeError as e:
Joe Gregorioca876e42011-02-22 19:39:42 -0500435 self.assertTrue('not an allowed value' in str(e))
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400436
437 # Unexpected parameter
438 try:
Joe Gregorio7b70f432011-11-09 10:18:51 -0500439 plus.activities().list(flubber=12)
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400440 self.fail()
INADA Naokic1505df2014-08-20 15:19:53 +0900441 except TypeError as e:
Joe Gregorioba9ea7f2010-08-19 15:49:04 -0400442 self.assertTrue('unexpected' in str(e))
443
Joe Gregoriobee86832011-02-22 10:00:19 -0500444 def _check_query_types(self, request):
445 parsed = urlparse.urlparse(request.uri)
446 q = parse_qs(parsed[4])
447 self.assertEqual(q['q'], ['foo'])
448 self.assertEqual(q['i'], ['1'])
449 self.assertEqual(q['n'], ['1.0'])
450 self.assertEqual(q['b'], ['false'])
451 self.assertEqual(q['a'], ['[1, 2, 3]'])
452 self.assertEqual(q['o'], ['{\'a\': 1}'])
453 self.assertEqual(q['e'], ['bar'])
454
455 def test_type_coercion(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400456 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400457 zoo = build('zoo', 'v1', http=http)
Joe Gregoriobee86832011-02-22 10:00:19 -0500458
Joe Gregorioa98733f2011-09-16 10:12:28 -0400459 request = zoo.query(
460 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 -0500461 self._check_query_types(request)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400462 request = zoo.query(
463 q="foo", i=1, n=1, b=False, a=[1,2,3], o={'a':1}, e='bar')
Joe Gregoriobee86832011-02-22 10:00:19 -0500464 self._check_query_types(request)
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500465
Joe Gregorioa98733f2011-09-16 10:12:28 -0400466 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800467 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 -0500468
469 request = zoo.query(
Craig Citro1e742822012-03-01 12:59:22 -0800470 q="foo", i="1", n="1", b="", a=[1,2,3], o={'a':1}, e='bar',
471 er=['one', 'three'], rr=['foo', 'bar'])
Joe Gregoriobee86832011-02-22 10:00:19 -0500472 self._check_query_types(request)
473
Craig Citro1e742822012-03-01 12:59:22 -0800474 # Five is right out.
Joe Gregorio20c26e52012-03-02 15:58:31 -0500475 self.assertRaises(TypeError, zoo.query, er=['one', 'five'])
Craig Citro1e742822012-03-01 12:59:22 -0800476
Joe Gregorio13217952011-02-22 15:37:38 -0500477 def test_optional_stack_query_parameters(self):
Joe Gregoriof4153422011-03-18 22:45:18 -0400478 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400479 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400480 request = zoo.query(trace='html', fields='description')
Joe Gregorio13217952011-02-22 15:37:38 -0500481
Joe Gregorioca876e42011-02-22 19:39:42 -0500482 parsed = urlparse.urlparse(request.uri)
483 q = parse_qs(parsed[4])
484 self.assertEqual(q['trace'], ['html'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400485 self.assertEqual(q['fields'], ['description'])
486
Joe Gregorio2467afa2012-06-20 12:21:25 -0400487 def test_string_params_value_of_none_get_dropped(self):
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400488 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400489 zoo = build('zoo', 'v1', http=http)
Joe Gregorio2467afa2012-06-20 12:21:25 -0400490 request = zoo.query(trace=None, fields='description')
491
492 parsed = urlparse.urlparse(request.uri)
493 q = parse_qs(parsed[4])
494 self.assertFalse('trace' in q)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400495
Joe Gregorioe08a1662011-12-07 09:48:22 -0500496 def test_model_added_query_parameters(self):
497 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400498 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500499 request = zoo.animals().get(name='Lion')
500
501 parsed = urlparse.urlparse(request.uri)
502 q = parse_qs(parsed[4])
503 self.assertEqual(q['alt'], ['json'])
504 self.assertEqual(request.headers['accept'], 'application/json')
505
506 def test_fallback_to_raw_model(self):
507 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400508 zoo = build('zoo', 'v1', http=http)
Joe Gregorioe08a1662011-12-07 09:48:22 -0500509 request = zoo.animals().getmedia(name='Lion')
510
511 parsed = urlparse.urlparse(request.uri)
512 q = parse_qs(parsed[4])
513 self.assertTrue('alt' not in q)
514 self.assertEqual(request.headers['accept'], '*/*')
515
Joe Gregoriof4153422011-03-18 22:45:18 -0400516 def test_patch(self):
517 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400518 zoo = build('zoo', 'v1', http=http)
Joe Gregoriof4153422011-03-18 22:45:18 -0400519 request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
520
521 self.assertEqual(request.method, 'PATCH')
522
523 def test_tunnel_patch(self):
524 http = HttpMockSequence([
Joe Gregorio79daca02013-03-29 16:25:52 -0400525 ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
Joe Gregoriof4153422011-03-18 22:45:18 -0400526 ({'status': '200'}, 'echo_request_headers_as_json'),
527 ])
528 http = tunnel_patch(http)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400529 zoo = build('zoo', 'v1', http=http)
Joe Gregorioa98733f2011-09-16 10:12:28 -0400530 resp = zoo.animals().patch(
531 name='lion', body='{"description": "foo"}').execute()
Joe Gregoriof4153422011-03-18 22:45:18 -0400532
533 self.assertTrue('x-http-method-override' in resp)
Joe Gregorioca876e42011-02-22 19:39:42 -0500534
Joe Gregorio7b70f432011-11-09 10:18:51 -0500535 def test_plus_resources(self):
536 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400537 plus = build('plus', 'v1', http=self.http)
Joe Gregorio7b70f432011-11-09 10:18:51 -0500538 self.assertTrue(getattr(plus, 'activities'))
539 self.assertTrue(getattr(plus, 'people'))
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400540
Orest Bolohane92c9002014-05-30 11:15:43 -0700541 def test_credentials(self):
542 class CredentialsMock:
543 def create_scoped_required(self):
544 return False
545
546 def authorize(self, http):
547 http.orest = True
548
549 self.http = HttpMock(datafile('plus.json'), {'status': '200'})
550 build('plus', 'v1', http=self.http, credentials=None)
551 self.assertFalse(hasattr(self.http, 'orest'))
552 build('plus', 'v1', http=self.http, credentials=CredentialsMock())
553 self.assertTrue(hasattr(self.http, 'orest'))
554
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400555 def test_full_featured(self):
556 # Zoo should exercise all discovery facets
557 # and should also have no future.json file.
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500558 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400559 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400560 self.assertTrue(getattr(zoo, 'animals'))
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500561
Joe Gregoriof4153422011-03-18 22:45:18 -0400562 request = zoo.animals().list(name='bat', projection="full")
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400563 parsed = urlparse.urlparse(request.uri)
564 q = parse_qs(parsed[4])
565 self.assertEqual(q['name'], ['bat'])
Joe Gregoriof4153422011-03-18 22:45:18 -0400566 self.assertEqual(q['projection'], ['full'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400567
Joe Gregorio3fada332011-01-07 17:07:45 -0500568 def test_nested_resources(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500569 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400570 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio3fada332011-01-07 17:07:45 -0500571 self.assertTrue(getattr(zoo, 'animals'))
572 request = zoo.my().favorites().list(max_results="5")
573 parsed = urlparse.urlparse(request.uri)
574 q = parse_qs(parsed[4])
575 self.assertEqual(q['max-results'], ['5'])
576
Joe Gregoriod92897c2011-07-07 11:44:56 -0400577 def test_methods_with_reserved_names(self):
578 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400579 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod92897c2011-07-07 11:44:56 -0400580 self.assertTrue(getattr(zoo, 'animals'))
581 request = zoo.global_().print_().assert_(max_results="5")
582 parsed = urlparse.urlparse(request.uri)
Joe Gregorioa2838152012-07-16 11:52:17 -0400583 self.assertEqual(parsed[2], '/zoo/v1/global/print/assert')
Joe Gregoriod92897c2011-07-07 11:44:56 -0400584
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500585 def test_top_level_functions(self):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500586 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400587 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500588 self.assertTrue(getattr(zoo, 'query'))
589 request = zoo.query(q="foo")
590 parsed = urlparse.urlparse(request.uri)
591 q = parse_qs(parsed[4])
592 self.assertEqual(q['q'], ['foo'])
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400593
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400594 def test_simple_media_uploads(self):
595 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400596 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400597 doc = getattr(zoo.animals().insert, '__doc__')
598 self.assertTrue('media_body' in doc)
599
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400600 def test_simple_media_upload_no_max_size_provided(self):
601 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400602 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio84d3c1f2011-07-25 10:39:45 -0400603 request = zoo.animals().crossbreed(media_body=datafile('small.png'))
604 self.assertEquals('image/png', request.headers['content-type'])
605 self.assertEquals('PNG', request.body[1:4])
606
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400607 def test_simple_media_raise_correct_exceptions(self):
608 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400609 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400610
611 try:
612 zoo.animals().insert(media_body=datafile('smiley.png'))
613 self.fail("should throw exception if media is too large.")
614 except MediaUploadSizeError:
615 pass
616
617 try:
618 zoo.animals().insert(media_body=datafile('small.jpg'))
619 self.fail("should throw exception if mimetype is unacceptable.")
620 except UnacceptableMimeTypeError:
621 pass
622
623 def test_simple_media_good_upload(self):
624 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400625 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400626
627 request = zoo.animals().insert(media_body=datafile('small.png'))
628 self.assertEquals('image/png', request.headers['content-type'])
629 self.assertEquals('PNG', request.body[1:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500630 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400631 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=media&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500632 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400633
634 def test_multipart_media_raise_correct_exceptions(self):
635 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400636 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400637
638 try:
639 zoo.animals().insert(media_body=datafile('smiley.png'), body={})
640 self.fail("should throw exception if media is too large.")
641 except MediaUploadSizeError:
642 pass
643
644 try:
645 zoo.animals().insert(media_body=datafile('small.jpg'), body={})
646 self.fail("should throw exception if mimetype is unacceptable.")
647 except UnacceptableMimeTypeError:
648 pass
649
650 def test_multipart_media_good_upload(self):
651 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400652 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400653
654 request = zoo.animals().insert(media_body=datafile('small.png'), body={})
Joe Gregorioa98733f2011-09-16 10:12:28 -0400655 self.assertTrue(request.headers['content-type'].startswith(
656 'multipart/related'))
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400657 self.assertEquals('--==', request.body[0:4])
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500658 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400659 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=multipart&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500660 request.uri)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400661
662 def test_media_capable_method_without_media(self):
663 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400664 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriofdf7c802011-06-30 12:33:38 -0400665
666 request = zoo.animals().insert(body={})
667 self.assertTrue(request.headers['content-type'], 'application/json')
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400668
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500669 def test_resumable_multipart_media_good_upload(self):
670 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400671 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500672
673 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
674 request = zoo.animals().insert(media_body=media_upload, body={})
675 self.assertTrue(request.headers['content-type'].startswith(
Joe Gregorio945be3e2012-01-27 17:01:06 -0500676 'application/json'))
677 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500678 self.assertEquals(media_upload, request.resumable)
679
680 self.assertEquals('image/png', request.resumable.mimetype())
681
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500682 self.assertNotEquals(request.body, None)
683 self.assertEquals(request.resumable_uri, None)
684
685 http = HttpMockSequence([
686 ({'status': '200',
687 'location': 'http://upload.example.com'}, ''),
688 ({'status': '308',
689 'location': 'http://upload.example.com/2',
690 'range': '0-12'}, ''),
691 ({'status': '308',
692 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500693 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500694 ({'status': '200'}, '{"foo": "bar"}'),
695 ])
696
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400697 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500698 self.assertEquals(None, body)
699 self.assertTrue(isinstance(status, MediaUploadProgress))
700 self.assertEquals(13, status.resumable_progress)
701
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500702 # Two requests should have been made and the resumable_uri should have been
703 # updated for each one.
704 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
705
706 self.assertEquals(media_upload, request.resumable)
707 self.assertEquals(13, request.resumable_progress)
708
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400709 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500710 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500711 self.assertEquals(media_upload.size()-1, request.resumable_progress)
712 self.assertEquals('{"data": {}}', request.body)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500713
714 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400715 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500716 self.assertEquals(body, {"foo": "bar"})
717 self.assertEquals(status, None)
718
719
720 def test_resumable_media_good_upload(self):
721 """Not a multipart upload."""
722 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400723 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500724
725 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
726 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500727 self.assertEquals(media_upload, request.resumable)
728
729 self.assertEquals('image/png', request.resumable.mimetype())
730
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500731 self.assertEquals(request.body, None)
732 self.assertEquals(request.resumable_uri, None)
733
734 http = HttpMockSequence([
735 ({'status': '200',
736 'location': 'http://upload.example.com'}, ''),
737 ({'status': '308',
738 'location': 'http://upload.example.com/2',
739 'range': '0-12'}, ''),
740 ({'status': '308',
741 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500742 'range': '0-%d' % (media_upload.size() - 2)}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500743 ({'status': '200'}, '{"foo": "bar"}'),
744 ])
745
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400746 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500747 self.assertEquals(None, body)
748 self.assertTrue(isinstance(status, MediaUploadProgress))
749 self.assertEquals(13, status.resumable_progress)
750
751 # Two requests should have been made and the resumable_uri should have been
752 # updated for each one.
753 self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
754
755 self.assertEquals(media_upload, request.resumable)
756 self.assertEquals(13, request.resumable_progress)
757
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400758 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500759 self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
Joe Gregorio945be3e2012-01-27 17:01:06 -0500760 self.assertEquals(media_upload.size()-1, request.resumable_progress)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500761 self.assertEquals(request.body, None)
762
763 # Final call to next_chunk should complete the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400764 status, body = request.next_chunk(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500765 self.assertEquals(body, {"foo": "bar"})
766 self.assertEquals(status, None)
767
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500768 def test_resumable_media_good_upload_from_execute(self):
769 """Not a multipart upload."""
770 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400771 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500772
773 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
774 request = zoo.animals().insert(media_body=media_upload, body=None)
Joe Gregoriof1ba7f12012-11-16 15:10:47 -0500775 assertUrisEqual(self,
Joe Gregorioa2838152012-07-16 11:52:17 -0400776 'https://www.googleapis.com/upload/zoo/v1/animals?uploadType=resumable&alt=json',
Joe Gregoriode860442012-03-02 15:55:52 -0500777 request.uri)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500778
779 http = HttpMockSequence([
780 ({'status': '200',
781 'location': 'http://upload.example.com'}, ''),
782 ({'status': '308',
783 'location': 'http://upload.example.com/2',
784 'range': '0-12'}, ''),
785 ({'status': '308',
786 'location': 'http://upload.example.com/3',
Joe Gregorio945be3e2012-01-27 17:01:06 -0500787 'range': '0-%d' % media_upload.size()}, ''),
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500788 ({'status': '200'}, '{"foo": "bar"}'),
789 ])
790
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400791 body = request.execute(http=http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500792 self.assertEquals(body, {"foo": "bar"})
793
794 def test_resumable_media_fail_unknown_response_code_first_request(self):
795 """Not a multipart upload."""
796 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400797 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500798
799 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
800 request = zoo.animals().insert(media_body=media_upload, body=None)
801
802 http = HttpMockSequence([
803 ({'status': '400',
804 'location': 'http://upload.example.com'}, ''),
805 ])
806
Joe Gregoriobaf04802013-03-01 12:27:06 -0500807 try:
808 request.execute(http=http)
809 self.fail('Should have raised ResumableUploadError.')
INADA Naokic1505df2014-08-20 15:19:53 +0900810 except ResumableUploadError as e:
Joe Gregoriobaf04802013-03-01 12:27:06 -0500811 self.assertEqual(400, e.resp.status)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500812
813 def test_resumable_media_fail_unknown_response_code_subsequent_request(self):
814 """Not a multipart upload."""
815 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400816 zoo = build('zoo', 'v1', http=self.http)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500817
818 media_upload = MediaFileUpload(datafile('small.png'), resumable=True)
819 request = zoo.animals().insert(media_body=media_upload, body=None)
820
821 http = HttpMockSequence([
822 ({'status': '200',
823 'location': 'http://upload.example.com'}, ''),
824 ({'status': '400'}, ''),
825 ])
826
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400827 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400828 self.assertTrue(request._in_error_state)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500829
Joe Gregorio910b9b12012-06-12 09:36:30 -0400830 http = HttpMockSequence([
831 ({'status': '308',
832 'range': '0-5'}, ''),
833 ({'status': '308',
834 'range': '0-6'}, ''),
835 ])
836
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400837 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400838 self.assertEquals(status.resumable_progress, 7,
839 'Should have first checked length and then tried to PUT more.')
840 self.assertFalse(request._in_error_state)
841
842 # Put it back in an error state.
843 http = HttpMockSequence([
844 ({'status': '400'}, ''),
845 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400846 self.assertRaises(HttpError, request.execute, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400847 self.assertTrue(request._in_error_state)
848
849 # Pretend the last request that 400'd actually succeeded.
850 http = HttpMockSequence([
851 ({'status': '200'}, '{"foo": "bar"}'),
852 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400853 status, body = request.next_chunk(http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400854 self.assertEqual(body, {'foo': 'bar'})
855
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400856 def test_media_io_base_stream_unlimited_chunksize_resume(self):
857 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
858 zoo = build('zoo', 'v1', http=self.http)
859
860 try:
861 import io
862
863 # Set up a seekable stream and try to upload in single chunk.
864 fd = io.BytesIO('01234"56789"')
865 media_upload = MediaIoBaseUpload(
866 fd=fd, mimetype='text/plain', chunksize=-1, resumable=True)
867
868 request = zoo.animals().insert(media_body=media_upload, body=None)
869
870 # The single chunk fails, restart at the right point.
871 http = HttpMockSequence([
872 ({'status': '200',
873 'location': 'http://upload.example.com'}, ''),
874 ({'status': '308',
875 'location': 'http://upload.example.com/2',
876 'range': '0-4'}, ''),
877 ({'status': '200'}, 'echo_request_body'),
878 ])
879
880 body = request.execute(http=http)
881 self.assertEqual('56789', body)
882
883 except ImportError:
884 pass
885
Joe Gregorio5c120db2012-08-23 09:13:55 -0400886
887 def test_media_io_base_stream_chunksize_resume(self):
888 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
889 zoo = build('zoo', 'v1', http=self.http)
890
891 try:
892 import io
893
894 # Set up a seekable stream and try to upload in chunks.
895 fd = io.BytesIO('0123456789')
896 media_upload = MediaIoBaseUpload(
897 fd=fd, mimetype='text/plain', chunksize=5, resumable=True)
898
899 request = zoo.animals().insert(media_body=media_upload, body=None)
900
901 # The single chunk fails, pull the content sent out of the exception.
902 http = HttpMockSequence([
903 ({'status': '200',
904 'location': 'http://upload.example.com'}, ''),
905 ({'status': '400'}, 'echo_request_body'),
906 ])
907
908 try:
909 body = request.execute(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +0900910 except HttpError as e:
Joe Gregorio5c120db2012-08-23 09:13:55 -0400911 self.assertEqual('01234', e.content)
912
913 except ImportError:
914 pass
915
916
Joe Gregorio910b9b12012-06-12 09:36:30 -0400917 def test_resumable_media_handle_uploads_of_unknown_size(self):
918 http = HttpMockSequence([
919 ({'status': '200',
920 'location': 'http://upload.example.com'}, ''),
921 ({'status': '200'}, 'echo_request_headers_as_json'),
922 ])
923
924 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400925 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -0400926
Joe Gregorio910b9b12012-06-12 09:36:30 -0400927 # Create an upload that doesn't know the full size of the media.
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400928 class IoBaseUnknownLength(MediaUpload):
929 def chunksize(self):
930 return 10
931
932 def mimetype(self):
933 return 'image/png'
934
935 def size(self):
936 return None
937
938 def resumable(self):
939 return True
940
941 def getbytes(self, begin, length):
942 return '0123456789'
943
944 upload = IoBaseUnknownLength()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400945
946 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400947 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400948 self.assertEqual(body, {
949 'Content-Range': 'bytes 0-9/*',
950 'Content-Length': '10',
951 })
Joe Gregorio44454e42012-06-15 08:38:53 -0400952
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400953 def test_resumable_media_no_streaming_on_unsupported_platforms(self):
954 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
955 zoo = build('zoo', 'v1', http=self.http)
956
957 class IoBaseHasStream(MediaUpload):
958 def chunksize(self):
959 return 10
960
961 def mimetype(self):
962 return 'image/png'
963
964 def size(self):
965 return None
966
967 def resumable(self):
968 return True
969
970 def getbytes(self, begin, length):
971 return '0123456789'
972
973 def has_stream(self):
974 return True
975
976 def stream(self):
977 raise NotImplementedError()
978
979 upload = IoBaseHasStream()
980
981 orig_version = sys.version_info
982 sys.version_info = (2, 5, 5, 'final', 0)
983
984 request = zoo.animals().insert(media_body=upload, body=None)
985
986 http = HttpMockSequence([
987 ({'status': '200',
988 'location': 'http://upload.example.com'}, ''),
989 ({'status': '200'}, 'echo_request_headers_as_json'),
990 ])
991
992 # This should not raise an exception because stream() shouldn't be called.
993 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -0400994 self.assertEqual(body, {
995 'Content-Range': 'bytes 0-9/*',
996 'Content-Length': '10'
997 })
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400998
999 sys.version_info = (2, 6, 5, 'final', 0)
1000
1001 request = zoo.animals().insert(media_body=upload, body=None)
1002
1003 # This should raise an exception because stream() will be called.
1004 http = HttpMockSequence([
1005 ({'status': '200',
1006 'location': 'http://upload.example.com'}, ''),
1007 ({'status': '200'}, 'echo_request_headers_as_json'),
1008 ])
1009
1010 self.assertRaises(NotImplementedError, request.next_chunk, http=http)
1011
1012 sys.version_info = orig_version
1013
Joe Gregorio44454e42012-06-15 08:38:53 -04001014 def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
1015 http = HttpMockSequence([
1016 ({'status': '200',
1017 'location': 'http://upload.example.com'}, ''),
1018 ({'status': '200'}, 'echo_request_headers_as_json'),
1019 ])
1020
1021 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001022 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio44454e42012-06-15 08:38:53 -04001023
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001024 fd = StringIO.StringIO('data goes here')
Joe Gregorio44454e42012-06-15 08:38:53 -04001025
1026 # Create an upload that doesn't know the full size of the media.
1027 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001028 fd=fd, mimetype='image/png', chunksize=15, resumable=True)
Joe Gregorio44454e42012-06-15 08:38:53 -04001029
1030 request = zoo.animals().insert(media_body=upload, body=None)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001031 status, body = request.next_chunk(http=http)
Joe Gregorio5c120db2012-08-23 09:13:55 -04001032 self.assertEqual(body, {
1033 'Content-Range': 'bytes 0-13/14',
1034 'Content-Length': '14',
1035 })
Joe Gregorio910b9b12012-06-12 09:36:30 -04001036
1037 def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
1038 http = HttpMockSequence([
1039 ({'status': '200',
1040 'location': 'http://upload.example.com'}, ''),
1041 ({'status': '400'}, ''),
1042 ])
1043
1044 self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001045 zoo = build('zoo', 'v1', http=self.http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001046
1047 # Create an upload that doesn't know the full size of the media.
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001048 fd = StringIO.StringIO('data goes here')
Joe Gregorio910b9b12012-06-12 09:36:30 -04001049
1050 upload = MediaIoBaseUpload(
Joe Gregorio4a2c29f2012-07-12 12:52:47 -04001051 fd=fd, mimetype='image/png', chunksize=500, resumable=True)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001052
1053 request = zoo.animals().insert(media_body=upload, body=None)
1054
1055 # Put it in an error state.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001056 self.assertRaises(HttpError, request.next_chunk, http=http)
Joe Gregorio910b9b12012-06-12 09:36:30 -04001057
1058 http = HttpMockSequence([
1059 ({'status': '400',
1060 'range': '0-5'}, 'echo_request_headers_as_json'),
1061 ])
1062 try:
1063 # Should resume the upload by first querying the status of the upload.
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001064 request.next_chunk(http=http)
INADA Naokic1505df2014-08-20 15:19:53 +09001065 except HttpError as e:
Joe Gregorio910b9b12012-06-12 09:36:30 -04001066 expected = {
Joe Gregorioc80ac9d2012-08-21 14:09:09 -04001067 'Content-Range': 'bytes */14',
Joe Gregorio910b9b12012-06-12 09:36:30 -04001068 'content-length': '0'
1069 }
Craig Citro6ae34d72014-08-18 23:10:09 -07001070 self.assertEqual(expected, json.loads(e.content),
Joe Gregorio910b9b12012-06-12 09:36:30 -04001071 'Should send an empty body when requesting the current upload status.')
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001072
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001073 def test_pickle(self):
1074 sorted_resource_keys = ['_baseUrl',
1075 '_developerKey',
1076 '_dynamic_attrs',
1077 '_http',
1078 '_model',
1079 '_requestBuilder',
1080 '_resourceDesc',
1081 '_rootDesc',
1082 '_schema',
1083 'animals',
1084 'global_',
1085 'load',
1086 'loadNoTemplate',
1087 'my',
1088 'query',
1089 'scopedAnimals']
1090
1091 http = HttpMock(datafile('zoo.json'), {'status': '200'})
1092 zoo = build('zoo', 'v1', http=http)
1093 self.assertEqual(sorted(zoo.__dict__.keys()), sorted_resource_keys)
1094
1095 pickled_zoo = pickle.dumps(zoo)
1096 new_zoo = pickle.loads(pickled_zoo)
1097 self.assertEqual(sorted(new_zoo.__dict__.keys()), sorted_resource_keys)
1098 self.assertTrue(hasattr(new_zoo, 'animals'))
1099 self.assertTrue(callable(new_zoo.animals))
1100 self.assertTrue(hasattr(new_zoo, 'global_'))
1101 self.assertTrue(callable(new_zoo.global_))
1102 self.assertTrue(hasattr(new_zoo, 'load'))
1103 self.assertTrue(callable(new_zoo.load))
1104 self.assertTrue(hasattr(new_zoo, 'loadNoTemplate'))
1105 self.assertTrue(callable(new_zoo.loadNoTemplate))
1106 self.assertTrue(hasattr(new_zoo, 'my'))
1107 self.assertTrue(callable(new_zoo.my))
1108 self.assertTrue(hasattr(new_zoo, 'query'))
1109 self.assertTrue(callable(new_zoo.query))
1110 self.assertTrue(hasattr(new_zoo, 'scopedAnimals'))
1111 self.assertTrue(callable(new_zoo.scopedAnimals))
1112
Joe Gregorio003b6e42013-02-13 15:42:19 -05001113 self.assertEqual(sorted(zoo._dynamic_attrs), sorted(new_zoo._dynamic_attrs))
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001114 self.assertEqual(zoo._baseUrl, new_zoo._baseUrl)
1115 self.assertEqual(zoo._developerKey, new_zoo._developerKey)
1116 self.assertEqual(zoo._requestBuilder, new_zoo._requestBuilder)
1117 self.assertEqual(zoo._resourceDesc, new_zoo._resourceDesc)
1118 self.assertEqual(zoo._rootDesc, new_zoo._rootDesc)
1119 # _http, _model and _schema won't be equal since we will get new
1120 # instances upon un-pickling
1121
1122 def _dummy_zoo_request(self):
1123 with open(os.path.join(DATA_DIR, 'zoo.json'), 'rU') as fh:
1124 zoo_contents = fh.read()
1125
1126 zoo_uri = uritemplate.expand(DISCOVERY_URI,
1127 {'api': 'zoo', 'apiVersion': 'v1'})
1128 if 'REMOTE_ADDR' in os.environ:
Joe Gregorio79daca02013-03-29 16:25:52 -04001129 zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
1130 os.environ['REMOTE_ADDR'])
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001131
1132 http = httplib2.Http()
1133 original_request = http.request
1134 def wrapped_request(uri, method='GET', *args, **kwargs):
1135 if uri == zoo_uri:
1136 return httplib2.Response({'status': '200'}), zoo_contents
1137 return original_request(uri, method=method, *args, **kwargs)
1138 http.request = wrapped_request
1139 return http
1140
1141 def _dummy_token(self):
1142 access_token = 'foo'
1143 client_id = 'some_client_id'
1144 client_secret = 'cOuDdkfjxxnv+'
1145 refresh_token = '1/0/a.df219fjls0'
1146 token_expiry = datetime.datetime.utcnow()
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001147 user_agent = 'refresh_checker/1.0'
1148 return OAuth2Credentials(
1149 access_token, client_id, client_secret,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -08001150 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001151 user_agent)
1152
Joe Gregoriodc106fc2012-11-20 14:30:14 -05001153 def test_pickle_with_credentials(self):
1154 credentials = self._dummy_token()
1155 http = self._dummy_zoo_request()
1156 http = credentials.authorize(http)
1157 self.assertTrue(hasattr(http.request, 'credentials'))
1158
1159 zoo = build('zoo', 'v1', http=http)
1160 pickled_zoo = pickle.dumps(zoo)
1161 new_zoo = pickle.loads(pickled_zoo)
1162 self.assertEqual(sorted(zoo.__dict__.keys()),
1163 sorted(new_zoo.__dict__.keys()))
1164 new_http = new_zoo._http
1165 self.assertFalse(hasattr(new_http.request, 'credentials'))
1166
Joe Gregorio708388c2012-06-15 13:43:04 -04001167
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001168class Next(unittest.TestCase):
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001169
Joe Gregorio3c676f92011-07-25 10:38:14 -04001170 def test_next_successful_none_on_no_next_page_token(self):
1171 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001172 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001173 request = tasks.tasklists().list()
1174 self.assertEqual(None, tasks.tasklists().list_next(request, {}))
1175
1176 def test_next_successful_with_next_page_token(self):
1177 self.http = HttpMock(datafile('tasks.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001178 tasks = build('tasks', 'v1', http=self.http)
Joe Gregorio3c676f92011-07-25 10:38:14 -04001179 request = tasks.tasklists().list()
Joe Gregorioa98733f2011-09-16 10:12:28 -04001180 next_request = tasks.tasklists().list_next(
1181 request, {'nextPageToken': '123abc'})
Joe Gregorio3c676f92011-07-25 10:38:14 -04001182 parsed = list(urlparse.urlparse(next_request.uri))
1183 q = parse_qs(parsed[4])
1184 self.assertEqual(q['pageToken'][0], '123abc')
1185
Joe Gregorio555f33c2011-08-19 14:56:07 -04001186 def test_next_with_method_with_no_properties(self):
1187 self.http = HttpMock(datafile('latitude.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001188 service = build('latitude', 'v1', http=self.http)
Joe Gregorio555f33c2011-08-19 14:56:07 -04001189 request = service.currentLocation().get()
Joe Gregorio00cf1d92010-09-27 09:22:03 -04001190
Joe Gregorioa98733f2011-09-16 10:12:28 -04001191
Joe Gregorio708388c2012-06-15 13:43:04 -04001192class MediaGet(unittest.TestCase):
1193
1194 def test_get_media(self):
1195 http = HttpMock(datafile('zoo.json'), {'status': '200'})
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001196 zoo = build('zoo', 'v1', http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001197 request = zoo.animals().get_media(name='Lion')
1198
1199 parsed = urlparse.urlparse(request.uri)
1200 q = parse_qs(parsed[4])
1201 self.assertEqual(q['alt'], ['media'])
1202 self.assertEqual(request.headers['accept'], '*/*')
1203
1204 http = HttpMockSequence([
1205 ({'status': '200'}, 'standing in for media'),
1206 ])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001207 response = request.execute(http=http)
Joe Gregorio708388c2012-06-15 13:43:04 -04001208 self.assertEqual('standing in for media', response)
1209
1210
Joe Gregorioba9ea7f2010-08-19 15:49:04 -04001211if __name__ == '__main__':
1212 unittest.main()