blob: c7d9cc35df796e621da3bc8d31b3d3a3d15cb2bb [file] [log] [blame]
Craig Citro15744b12015-03-02 13:34:32 -08001#!/usr/bin/env python
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05002#
Craig Citro751b7fb2014-09-23 11:20:38 -07003# Copyright 2014 Google Inc. All Rights Reserved.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Http tests
18
John Asmuth864311d2014-04-24 15:46:08 -040019Unit tests for the googleapiclient.http.
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050020"""
INADA Naokid898a372015-03-04 03:52:46 +090021from __future__ import absolute_import
22from six.moves import range
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050023
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070024__author__ = "jcgregorio@google.com (Joe Gregorio)"
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050025
Pat Feratec6050872015-03-03 18:24:59 -080026from six import PY3
Pat Ferateed9affd2015-03-03 16:03:15 -080027from six import BytesIO, StringIO
28from io import FileIO
Pat Ferated5b61bd2015-03-03 16:04:11 -080029from six.moves.urllib.parse import urlencode
Pat Ferateed9affd2015-03-03 16:03:15 -080030
Joe Gregorio7cbceab2011-06-27 10:46:54 -040031# Do not remove the httplib2 import
Chris McDonough0dc81bf2018-07-19 11:19:58 -040032import json
Joe Gregorio7cbceab2011-06-27 10:46:54 -040033import httplib2
Xiaofei Wang20b67582019-07-17 11:16:53 -070034import io
Joe Gregorio9086bd32013-06-14 16:32:05 -040035import logging
eesheeshc6425a02016-02-12 15:07:06 +000036import mock
Joe Gregoriod0bd3882011-11-22 09:49:47 -050037import os
Pat Ferate497a90f2015-03-09 09:52:54 -070038import unittest2 as unittest
Joe Gregorio9086bd32013-06-14 16:32:05 -040039import random
eesheeshc6425a02016-02-12 15:07:06 +000040import socket
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +010041import ssl
Joe Gregorio9086bd32013-06-14 16:32:05 -040042import time
Joe Gregorio6bcbcea2011-03-10 15:26:05 -050043
John Asmuth864311d2014-04-24 15:46:08 -040044from googleapiclient.discovery import build
45from googleapiclient.errors import BatchError
46from googleapiclient.errors import HttpError
47from googleapiclient.errors import InvalidChunkSizeError
Igor Maravić22435292017-01-19 22:28:22 +010048from googleapiclient.http import build_http
John Asmuth864311d2014-04-24 15:46:08 -040049from googleapiclient.http import BatchHttpRequest
50from googleapiclient.http import HttpMock
51from googleapiclient.http import HttpMockSequence
52from googleapiclient.http import HttpRequest
53from googleapiclient.http import MAX_URI_LENGTH
54from googleapiclient.http import MediaFileUpload
55from googleapiclient.http import MediaInMemoryUpload
56from googleapiclient.http import MediaIoBaseDownload
57from googleapiclient.http import MediaIoBaseUpload
58from googleapiclient.http import MediaUpload
59from googleapiclient.http import _StreamSlice
60from googleapiclient.http import set_user_agent
61from googleapiclient.model import JsonModel
Joe Gregorio654f4a22012-02-09 14:15:44 -050062from oauth2client.client import Credentials
63
64
65class MockCredentials(Credentials):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070066 """Mock class for all Credentials objects."""
Jon Wayne Parrottd3a5cf42017-06-19 17:55:04 -070067
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070068 def __init__(self, bearer_token, expired=False):
69 super(MockCredentials, self).__init__()
70 self._authorized = 0
71 self._refreshed = 0
72 self._applied = 0
73 self._bearer_token = bearer_token
74 self._access_token_expired = expired
Jon Wayne Parrott20e61352018-01-18 09:16:37 -080075
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070076 @property
77 def access_token(self):
78 return self._bearer_token
Joe Gregorio654f4a22012-02-09 14:15:44 -050079
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070080 @property
81 def access_token_expired(self):
82 return self._access_token_expired
Joe Gregorio654f4a22012-02-09 14:15:44 -050083
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070084 def authorize(self, http):
85 self._authorized += 1
Joe Gregorio654f4a22012-02-09 14:15:44 -050086
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070087 request_orig = http.request
Joe Gregorio654f4a22012-02-09 14:15:44 -050088
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070089 # The closure that will replace 'httplib2.Http.request'.
90 def new_request(
91 uri,
92 method="GET",
93 body=None,
94 headers=None,
95 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
96 connection_type=None,
97 ):
98 # Modify the request headers to add the appropriate
99 # Authorization header.
100 if headers is None:
101 headers = {}
102 self.apply(headers)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500103
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700104 resp, content = request_orig(
105 uri, method, body, headers, redirections, connection_type
106 )
Joe Gregorio654f4a22012-02-09 14:15:44 -0500107
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700108 return resp, content
Joe Gregorio654f4a22012-02-09 14:15:44 -0500109
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700110 # Replace the request method with our own closure.
111 http.request = new_request
Joe Gregorio654f4a22012-02-09 14:15:44 -0500112
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700113 # Set credentials as a property of the request method.
114 setattr(http.request, "credentials", self)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500115
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700116 return http
Joe Gregorio654f4a22012-02-09 14:15:44 -0500117
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700118 def refresh(self, http):
119 self._refreshed += 1
120
121 def apply(self, headers):
122 self._applied += 1
123 headers["authorization"] = self._bearer_token + " " + str(self._refreshed)
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500124
125
eesheeshc6425a02016-02-12 15:07:06 +0000126class HttpMockWithErrors(object):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700127 def __init__(self, num_errors, success_json, success_data):
128 self.num_errors = num_errors
129 self.success_json = success_json
130 self.success_data = success_data
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100131
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700132 def request(self, *args, **kwargs):
133 if not self.num_errors:
134 return httplib2.Response(self.success_json), self.success_data
Damian Gadomskic7516a22020-03-23 20:39:21 +0100135 elif self.num_errors == 5 and PY3:
136 ex = ConnectionResetError # noqa: F821
137 elif self.num_errors == 4:
138 ex = httplib2.ServerNotFoundError()
139 elif self.num_errors == 3:
140 ex = socket.error()
141 ex.errno = socket.errno.EPIPE
142 elif self.num_errors == 2:
143 ex = ssl.SSLError()
Thomas Bonfort88ab76b2016-04-19 08:48:53 +0200144 else:
Damian Gadomskic7516a22020-03-23 20:39:21 +0100145 # Initialize the timeout error code to the platform's error code.
146 try:
147 # For Windows:
148 ex = socket.error()
149 ex.errno = socket.errno.WSAETIMEDOUT
150 except AttributeError:
151 # For Linux/Mac:
152 if PY3:
153 ex = socket.timeout()
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700154 else:
Damian Gadomskic7516a22020-03-23 20:39:21 +0100155 ex = socket.error()
156 ex.errno = socket.errno.ETIMEDOUT
157
158 self.num_errors -= 1
159 raise ex
eesheeshc6425a02016-02-12 15:07:06 +0000160
161
162class HttpMockWithNonRetriableErrors(object):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700163 def __init__(self, num_errors, success_json, success_data):
164 self.num_errors = num_errors
165 self.success_json = success_json
166 self.success_data = success_data
eesheeshc6425a02016-02-12 15:07:06 +0000167
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700168 def request(self, *args, **kwargs):
169 if not self.num_errors:
170 return httplib2.Response(self.success_json), self.success_data
171 else:
172 self.num_errors -= 1
173 ex = socket.error()
174 # set errno to a non-retriable value
175 try:
176 # For Windows:
177 ex.errno = socket.errno.WSAECONNREFUSED
178 except AttributeError:
179 # For Linux/Mac:
180 ex.errno = socket.errno.ECONNREFUSED
181 # Now raise the correct timeout error.
182 raise ex
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100183
184
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700185DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500186
187
188def datafile(filename):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700189 return os.path.join(DATA_DIR, filename)
190
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500191
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100192def _postproc_none(*kwargs):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700193 pass
Ondrej Medekb86bfc92018-01-19 18:53:50 +0100194
195
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500196class TestUserAgent(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700197 def test_set_user_agent(self):
198 http = HttpMockSequence([({"status": "200"}, "echo_request_headers")])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500199
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700200 http = set_user_agent(http, "my_app/5.5")
201 resp, content = http.request("http://example.com")
202 self.assertEqual("my_app/5.5", content["user-agent"])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500203
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700204 def test_set_user_agent_nested(self):
205 http = HttpMockSequence([({"status": "200"}, "echo_request_headers")])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500206
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700207 http = set_user_agent(http, "my_app/5.5")
208 http = set_user_agent(http, "my_library/0.1")
209 resp, content = http.request("http://example.com")
210 self.assertEqual("my_app/5.5 my_library/0.1", content["user-agent"])
Joe Gregorio6bcbcea2011-03-10 15:26:05 -0500211
Joe Gregorio910b9b12012-06-12 09:36:30 -0400212
213class TestMediaUpload(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700214 def test_media_file_upload_closes_fd_in___del__(self):
215 file_desc = mock.Mock(spec=io.TextIOWrapper)
216 opener = mock.mock_open(file_desc)
217 if PY3:
218 with mock.patch("builtins.open", return_value=opener):
219 upload = MediaFileUpload(datafile("test_close"), mimetype="text/plain")
220 else:
221 with mock.patch("__builtin__.open", return_value=opener):
222 upload = MediaFileUpload(datafile("test_close"), mimetype="text/plain")
223 self.assertIs(upload.stream(), file_desc)
224 del upload
225 file_desc.close.assert_called_once_with()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400226
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700227 def test_media_file_upload_mimetype_detection(self):
228 upload = MediaFileUpload(datafile("small.png"))
229 self.assertEqual("image/png", upload.mimetype())
Xiaofei Wang20b67582019-07-17 11:16:53 -0700230
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700231 upload = MediaFileUpload(datafile("empty"))
232 self.assertEqual("application/octet-stream", upload.mimetype())
Nam T. Nguyendc136312015-12-01 10:18:56 -0800233
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700234 def test_media_file_upload_to_from_json(self):
235 upload = MediaFileUpload(datafile("small.png"), chunksize=500, resumable=True)
236 self.assertEqual("image/png", upload.mimetype())
237 self.assertEqual(190, upload.size())
238 self.assertEqual(True, upload.resumable())
239 self.assertEqual(500, upload.chunksize())
240 self.assertEqual(b"PNG", upload.getbytes(1, 3))
Nam T. Nguyendc136312015-12-01 10:18:56 -0800241
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700242 json = upload.to_json()
243 new_upload = MediaUpload.new_from_json(json)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500244
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700245 self.assertEqual("image/png", new_upload.mimetype())
246 self.assertEqual(190, new_upload.size())
247 self.assertEqual(True, new_upload.resumable())
248 self.assertEqual(500, new_upload.chunksize())
249 self.assertEqual(b"PNG", new_upload.getbytes(1, 3))
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500250
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700251 def test_media_file_upload_raises_on_invalid_chunksize(self):
252 self.assertRaises(
253 InvalidChunkSizeError,
254 MediaFileUpload,
255 datafile("small.png"),
256 mimetype="image/png",
257 chunksize=-2,
258 resumable=True,
259 )
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500260
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700261 def test_media_inmemory_upload(self):
262 media = MediaInMemoryUpload(
263 b"abcdef", mimetype="text/plain", chunksize=10, resumable=True
264 )
265 self.assertEqual("text/plain", media.mimetype())
266 self.assertEqual(10, media.chunksize())
267 self.assertTrue(media.resumable())
268 self.assertEqual(b"bc", media.getbytes(1, 2))
269 self.assertEqual(6, media.size())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400270
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700271 def test_http_request_to_from_json(self):
272 http = build_http()
273 media_upload = MediaFileUpload(
274 datafile("small.png"), chunksize=500, resumable=True
275 )
276 req = HttpRequest(
277 http,
278 _postproc_none,
279 "http://example.com",
280 method="POST",
281 body="{}",
282 headers={"content-type": 'multipart/related; boundary="---flubber"'},
283 methodId="foo",
284 resumable=media_upload,
285 )
Ali Afshar1cb6b672012-03-12 08:46:14 -0400286
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700287 json = req.to_json()
288 new_req = HttpRequest.from_json(json, http, _postproc_none)
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500289
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700290 self.assertEqual(
291 {"content-type": 'multipart/related; boundary="---flubber"'},
292 new_req.headers,
293 )
294 self.assertEqual("http://example.com", new_req.uri)
295 self.assertEqual("{}", new_req.body)
296 self.assertEqual(http, new_req.http)
297 self.assertEqual(media_upload.to_json(), new_req.resumable.to_json())
Joe Gregoriod0bd3882011-11-22 09:49:47 -0500298
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700299 self.assertEqual(random.random, new_req._rand)
300 self.assertEqual(time.sleep, new_req._sleep)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400301
Joe Gregorio910b9b12012-06-12 09:36:30 -0400302
303class TestMediaIoBaseUpload(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700304 def test_media_io_base_upload_from_file_io(self):
305 fd = FileIO(datafile("small.png"), "r")
306 upload = MediaIoBaseUpload(
307 fd=fd, mimetype="image/png", chunksize=500, resumable=True
308 )
309 self.assertEqual("image/png", upload.mimetype())
310 self.assertEqual(190, upload.size())
311 self.assertEqual(True, upload.resumable())
312 self.assertEqual(500, upload.chunksize())
313 self.assertEqual(b"PNG", upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400314
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700315 def test_media_io_base_upload_from_file_object(self):
316 f = open(datafile("small.png"), "rb")
317 upload = MediaIoBaseUpload(
318 fd=f, mimetype="image/png", chunksize=500, resumable=True
319 )
320 self.assertEqual("image/png", upload.mimetype())
321 self.assertEqual(190, upload.size())
322 self.assertEqual(True, upload.resumable())
323 self.assertEqual(500, upload.chunksize())
324 self.assertEqual(b"PNG", upload.getbytes(1, 3))
325 f.close()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400326
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700327 def test_media_io_base_upload_serializable(self):
328 f = open(datafile("small.png"), "rb")
329 upload = MediaIoBaseUpload(fd=f, mimetype="image/png")
Joe Gregorio910b9b12012-06-12 09:36:30 -0400330
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700331 try:
332 json = upload.to_json()
333 self.fail("MediaIoBaseUpload should not be serializable.")
334 except NotImplementedError:
335 pass
Joe Gregorio910b9b12012-06-12 09:36:30 -0400336
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700337 @unittest.skipIf(PY3, "Strings and Bytes are different types")
338 def test_media_io_base_upload_from_string_io(self):
339 f = open(datafile("small.png"), "rb")
340 fd = StringIO(f.read())
341 f.close()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400342
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700343 upload = MediaIoBaseUpload(
344 fd=fd, mimetype="image/png", chunksize=500, resumable=True
345 )
346 self.assertEqual("image/png", upload.mimetype())
347 self.assertEqual(190, upload.size())
348 self.assertEqual(True, upload.resumable())
349 self.assertEqual(500, upload.chunksize())
350 self.assertEqual(b"PNG", upload.getbytes(1, 3))
351 f.close()
Joe Gregorio910b9b12012-06-12 09:36:30 -0400352
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700353 def test_media_io_base_upload_from_bytes(self):
354 f = open(datafile("small.png"), "rb")
355 fd = BytesIO(f.read())
356 upload = MediaIoBaseUpload(
357 fd=fd, mimetype="image/png", chunksize=500, resumable=True
358 )
359 self.assertEqual("image/png", upload.mimetype())
360 self.assertEqual(190, upload.size())
361 self.assertEqual(True, upload.resumable())
362 self.assertEqual(500, upload.chunksize())
363 self.assertEqual(b"PNG", upload.getbytes(1, 3))
Joe Gregorio910b9b12012-06-12 09:36:30 -0400364
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700365 def test_media_io_base_upload_raises_on_invalid_chunksize(self):
366 f = open(datafile("small.png"), "rb")
367 fd = BytesIO(f.read())
368 self.assertRaises(
369 InvalidChunkSizeError,
370 MediaIoBaseUpload,
371 fd,
372 "image/png",
373 chunksize=-2,
374 resumable=True,
375 )
Joe Gregorio910b9b12012-06-12 09:36:30 -0400376
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700377 def test_media_io_base_upload_streamable(self):
378 fd = BytesIO(b"stuff")
379 upload = MediaIoBaseUpload(
380 fd=fd, mimetype="image/png", chunksize=500, resumable=True
381 )
382 self.assertEqual(True, upload.has_stream())
383 self.assertEqual(fd, upload.stream())
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400384
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700385 def test_media_io_base_next_chunk_retries(self):
386 f = open(datafile("small.png"), "rb")
387 fd = BytesIO(f.read())
388 upload = MediaIoBaseUpload(
389 fd=fd, mimetype="image/png", chunksize=500, resumable=True
390 )
Joe Gregorioc80ac9d2012-08-21 14:09:09 -0400391
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700392 # Simulate errors for both the request that creates the resumable upload
393 # and the upload itself.
394 http = HttpMockSequence(
395 [
396 ({"status": "500"}, ""),
397 ({"status": "500"}, ""),
398 ({"status": "503"}, ""),
399 ({"status": "200", "location": "location"}, ""),
400 ({"status": "403"}, USER_RATE_LIMIT_EXCEEDED_RESPONSE),
401 ({"status": "403"}, RATE_LIMIT_EXCEEDED_RESPONSE),
402 ({"status": "429"}, ""),
403 ({"status": "200"}, "{}"),
404 ]
405 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400406
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700407 model = JsonModel()
408 uri = u"https://www.googleapis.com/someapi/v1/upload/?foo=bar"
409 method = u"POST"
410 request = HttpRequest(
411 http, model.response, uri, method=method, headers={}, resumable=upload
412 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400413
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700414 sleeptimes = []
415 request._sleep = lambda x: sleeptimes.append(x)
416 request._rand = lambda: 10
Joe Gregorio9086bd32013-06-14 16:32:05 -0400417
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700418 request.execute(num_retries=3)
419 self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400420
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700421 def test_media_io_base_next_chunk_no_retry_403_not_configured(self):
422 fd = BytesIO(b"i am png")
423 upload = MediaIoBaseUpload(
424 fd=fd, mimetype="image/png", chunksize=500, resumable=True
425 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400426
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700427 http = HttpMockSequence(
428 [({"status": "403"}, NOT_CONFIGURED_RESPONSE), ({"status": "200"}, "{}")]
429 )
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500430
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700431 model = JsonModel()
432 uri = u"https://www.googleapis.com/someapi/v1/upload/?foo=bar"
433 method = u"POST"
434 request = HttpRequest(
435 http, model.response, uri, method=method, headers={}, resumable=upload
436 )
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500437
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700438 request._rand = lambda: 1.0
439 request._sleep = mock.MagicMock()
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500440
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700441 with self.assertRaises(HttpError):
442 request.execute(num_retries=3)
443 request._sleep.assert_not_called()
Zhihao Yuancc6d3982016-07-27 11:40:45 -0500444
Joe Gregorio910b9b12012-06-12 09:36:30 -0400445
Bu Sun Kimaf6035f2020-10-20 16:36:04 -0600446 def test_media_io_base_empty_file(self):
447 fd = BytesIO()
448 upload = MediaIoBaseUpload(
449 fd=fd, mimetype="image/png", chunksize=500, resumable=True
450 )
451
452 http = HttpMockSequence(
453 [
454 ({"status": "200", "location": "https://www.googleapis.com/someapi/v1/upload?foo=bar"}, "{}"),
455 ({"status": "200", "location": "https://www.googleapis.com/someapi/v1/upload?foo=bar"}, "{}")
456 ]
457 )
458
459 model = JsonModel()
460 uri = u"https://www.googleapis.com/someapi/v1/upload/?foo=bar"
461 method = u"POST"
462 request = HttpRequest(
463 http, model.response, uri, method=method, headers={}, resumable=upload
464 )
465
466 request.execute()
467
468 # Check that "Content-Range" header is not set in the PUT request
469 self.assertTrue("Content-Range" not in http.request_sequence[-1][-1])
470 self.assertEqual("0", http.request_sequence[-1][-1]["Content-Length"])
471
472
Joe Gregorio708388c2012-06-15 13:43:04 -0400473class TestMediaIoBaseDownload(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700474 def setUp(self):
475 http = HttpMock(datafile("zoo.json"), {"status": "200"})
476 zoo = build("zoo", "v1", http=http)
477 self.request = zoo.animals().get_media(name="Lion")
478 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400479
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700480 def test_media_io_base_download(self):
481 self.request.http = HttpMockSequence(
482 [
483 ({"status": "200", "content-range": "0-2/5"}, b"123"),
484 ({"status": "200", "content-range": "3-4/5"}, b"45"),
485 ]
486 )
487 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400488
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700489 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400490
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700491 self.assertEqual(self.fd, download._fd)
492 self.assertEqual(3, download._chunksize)
493 self.assertEqual(0, download._progress)
494 self.assertEqual(None, download._total_size)
495 self.assertEqual(False, download._done)
496 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400497
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700498 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400499
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700500 self.assertEqual(self.fd.getvalue(), b"123")
501 self.assertEqual(False, done)
502 self.assertEqual(3, download._progress)
503 self.assertEqual(5, download._total_size)
504 self.assertEqual(3, status.resumable_progress)
Joe Gregorio708388c2012-06-15 13:43:04 -0400505
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700506 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400507
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700508 self.assertEqual(self.fd.getvalue(), b"12345")
509 self.assertEqual(True, done)
510 self.assertEqual(5, download._progress)
511 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400512
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700513 def test_media_io_base_download_custom_request_headers(self):
514 self.request.http = HttpMockSequence(
515 [
516 (
517 {"status": "200", "content-range": "0-2/5"},
518 "echo_request_headers_as_json",
519 ),
520 (
521 {"status": "200", "content-range": "3-4/5"},
522 "echo_request_headers_as_json",
523 ),
524 ]
525 )
526 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400527
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700528 self.request.headers["Cache-Control"] = "no-store"
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400529
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700530 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400531
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700532 self.assertEqual(download._headers.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400533
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700534 status, done = download.next_chunk()
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400535
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700536 result = json.loads(self.fd.getvalue().decode("utf-8"))
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400537
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700538 # assert that that the header we added to the original request is
539 # sent up to the server on each call to next_chunk
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400540
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700541 self.assertEqual(result.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400542
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700543 download._fd = self.fd = BytesIO()
544 status, done = download.next_chunk()
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400545
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700546 result = json.loads(self.fd.getvalue().decode("utf-8"))
547 self.assertEqual(result.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400548
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700549 def test_media_io_base_download_handle_redirects(self):
550 self.request.http = HttpMockSequence(
551 [
552 (
553 {
554 "status": "200",
555 "content-location": "https://secure.example.net/lion",
556 },
557 b"",
558 ),
559 ({"status": "200", "content-range": "0-2/5"}, b"abc"),
560 ]
561 )
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400562
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700563 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400564
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700565 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400566
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700567 self.assertEqual("https://secure.example.net/lion", download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400568
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700569 def test_media_io_base_download_handle_4xx(self):
570 self.request.http = HttpMockSequence([({"status": "400"}, "")])
Joe Gregorio708388c2012-06-15 13:43:04 -0400571
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700572 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400573
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700574 try:
575 status, done = download.next_chunk()
576 self.fail("Should raise an exception")
577 except HttpError:
578 pass
Joe Gregorio708388c2012-06-15 13:43:04 -0400579
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700580 # Even after raising an exception we can pick up where we left off.
581 self.request.http = HttpMockSequence(
582 [({"status": "200", "content-range": "0-2/5"}, b"123")]
583 )
Joe Gregorio708388c2012-06-15 13:43:04 -0400584
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700585 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400586
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700587 self.assertEqual(self.fd.getvalue(), b"123")
Joe Gregorio708388c2012-06-15 13:43:04 -0400588
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700589 def test_media_io_base_download_retries_connection_errors(self):
590 self.request.http = HttpMockWithErrors(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100591 5, {"status": "200", "content-range": "0-2/3"}, b"123"
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700592 )
Joe Gregorio708388c2012-06-15 13:43:04 -0400593
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700594 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
595 download._sleep = lambda _x: 0 # do nothing
596 download._rand = lambda: 10
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100597
Damian Gadomskic7516a22020-03-23 20:39:21 +0100598 status, done = download.next_chunk(num_retries=5)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100599
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700600 self.assertEqual(self.fd.getvalue(), b"123")
601 self.assertEqual(True, done)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100602
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700603 def test_media_io_base_download_retries_5xx(self):
604 self.request.http = HttpMockSequence(
605 [
606 ({"status": "500"}, ""),
607 ({"status": "500"}, ""),
608 ({"status": "500"}, ""),
609 ({"status": "200", "content-range": "0-2/5"}, b"123"),
610 ({"status": "503"}, ""),
611 ({"status": "503"}, ""),
612 ({"status": "503"}, ""),
613 ({"status": "200", "content-range": "3-4/5"}, b"45"),
614 ]
615 )
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100616
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700617 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400618
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700619 self.assertEqual(self.fd, download._fd)
620 self.assertEqual(3, download._chunksize)
621 self.assertEqual(0, download._progress)
622 self.assertEqual(None, download._total_size)
623 self.assertEqual(False, download._done)
624 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400625
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700626 # Set time.sleep and random.random stubs.
627 sleeptimes = []
628 download._sleep = lambda x: sleeptimes.append(x)
629 download._rand = lambda: 10
Joe Gregorio9086bd32013-06-14 16:32:05 -0400630
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700631 status, done = download.next_chunk(num_retries=3)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400632
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700633 # Check for exponential backoff using the rand function above.
634 self.assertEqual([20, 40, 80], sleeptimes)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400635
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700636 self.assertEqual(self.fd.getvalue(), b"123")
637 self.assertEqual(False, done)
638 self.assertEqual(3, download._progress)
639 self.assertEqual(5, download._total_size)
640 self.assertEqual(3, status.resumable_progress)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400641
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700642 # Reset time.sleep stub.
643 del sleeptimes[0 : len(sleeptimes)]
Joe Gregorio9086bd32013-06-14 16:32:05 -0400644
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700645 status, done = download.next_chunk(num_retries=3)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400646
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700647 # Check for exponential backoff using the rand function above.
648 self.assertEqual([20, 40, 80], sleeptimes)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400649
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700650 self.assertEqual(self.fd.getvalue(), b"12345")
651 self.assertEqual(True, done)
652 self.assertEqual(5, download._progress)
653 self.assertEqual(5, download._total_size)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400654
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700655 def test_media_io_base_download_empty_file(self):
656 self.request.http = HttpMockSequence(
657 [({"status": "200", "content-range": "0-0/0"}, b"")]
658 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400659
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700660 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
andrewnestera4a44cf2017-03-31 16:09:31 +0300661
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700662 self.assertEqual(self.fd, download._fd)
663 self.assertEqual(0, download._progress)
664 self.assertEqual(None, download._total_size)
665 self.assertEqual(False, download._done)
666 self.assertEqual(self.request.uri, download._uri)
andrewnestera4a44cf2017-03-31 16:09:31 +0300667
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700668 status, done = download.next_chunk()
andrewnestera4a44cf2017-03-31 16:09:31 +0300669
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700670 self.assertEqual(True, done)
671 self.assertEqual(0, download._progress)
672 self.assertEqual(0, download._total_size)
673 self.assertEqual(0, status.progress())
andrewnestera4a44cf2017-03-31 16:09:31 +0300674
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700675 def test_media_io_base_download_unknown_media_size(self):
676 self.request.http = HttpMockSequence([({"status": "200"}, b"123")])
andrewnestera4a44cf2017-03-31 16:09:31 +0300677
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700678 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Daniel44067782018-01-16 23:17:56 +0100679
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700680 self.assertEqual(self.fd, download._fd)
681 self.assertEqual(0, download._progress)
682 self.assertEqual(None, download._total_size)
683 self.assertEqual(False, download._done)
684 self.assertEqual(self.request.uri, download._uri)
Daniel44067782018-01-16 23:17:56 +0100685
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700686 status, done = download.next_chunk()
Daniel44067782018-01-16 23:17:56 +0100687
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700688 self.assertEqual(self.fd.getvalue(), b"123")
689 self.assertEqual(True, done)
690 self.assertEqual(3, download._progress)
691 self.assertEqual(None, download._total_size)
692 self.assertEqual(0, status.progress())
Daniel44067782018-01-16 23:17:56 +0100693
694
Joe Gregorio66f57522011-11-30 11:00:00 -0500695EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
696Content-Type: application/json
697MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500698Host: www.googleapis.com
699content-length: 2\r\n\r\n{}"""
700
701
702NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
703Content-Type: application/json
704MIME-Version: 1.0
705Host: www.googleapis.com
706content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500707
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400708NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
709Content-Type: application/json
710MIME-Version: 1.0
711Host: www.googleapis.com\r\n\r\n"""
712
Joe Gregorio66f57522011-11-30 11:00:00 -0500713
714RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400715Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500716Content-Length: 14
717ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
718
719
INADA Naoki09157612015-03-25 01:51:03 +0900720BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500721Content-Type: application/http
722Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400723Content-ID: <randomness + 1>
Joe Gregorio66f57522011-11-30 11:00:00 -0500724
725HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400726Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500727Content-Length: 14
728ETag: "etag/pony"\r\n\r\n{"foo": 42}
729
730--batch_foobarbaz
731Content-Type: application/http
732Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400733Content-ID: <randomness + 2>
Joe Gregorio66f57522011-11-30 11:00:00 -0500734
735HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400736Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500737Content-Length: 14
738ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
739--batch_foobarbaz--"""
740
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500741
INADA Naoki09157612015-03-25 01:51:03 +0900742BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400743Content-Type: application/http
744Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400745Content-ID: <randomness + 1>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400746
747HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400748Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400749Content-Length: 14
750ETag: "etag/pony"\r\n\r\n{"foo": 42}
751
752--batch_foobarbaz
753Content-Type: application/http
754Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400755Content-ID: <randomness + 2>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400756
757HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400758Content-Type: application/json
759Content-Length: 245
760ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400761 "error": {
762 "errors": [
763 {
764 "domain": "usageLimits",
765 "reason": "accessNotConfigured",
766 "message": "Access Not Configured",
767 "debugInfo": "QuotaState: BLOCKED"
768 }
769 ],
770 "code": 403,
771 "message": "Access Not Configured"
772 }
773}
774
775--batch_foobarbaz--"""
776
777
INADA Naoki09157612015-03-25 01:51:03 +0900778BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500779Content-Type: application/http
780Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400781Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500782
Joe Gregorioc752e332012-07-11 14:43:52 -0400783HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400784Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500785Content-Length: 14
786ETag: "etag/pony"\r\n\r\n{"error": {"message":
787 "Authorizaton failed."}}
788
789--batch_foobarbaz
790Content-Type: application/http
791Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400792Content-ID: <randomness + 2>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500793
794HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400795Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500796Content-Length: 14
797ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
798--batch_foobarbaz--"""
799
800
INADA Naoki09157612015-03-25 01:51:03 +0900801BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500802Content-Type: application/http
803Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400804Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500805
806HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400807Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500808Content-Length: 14
809ETag: "etag/pony"\r\n\r\n{"foo": 42}
810--batch_foobarbaz--"""
811
eesheeshc6425a02016-02-12 15:07:06 +0000812
813USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
814 "error": {
815 "errors": [
816 {
817 "domain": "usageLimits",
818 "reason": "userRateLimitExceeded",
819 "message": "User Rate Limit Exceeded"
820 }
821 ],
822 "code": 403,
823 "message": "User Rate Limit Exceeded"
824 }
825}"""
826
827
828RATE_LIMIT_EXCEEDED_RESPONSE = """{
829 "error": {
830 "errors": [
831 {
832 "domain": "usageLimits",
833 "reason": "rateLimitExceeded",
834 "message": "Rate Limit Exceeded"
835 }
836 ],
837 "code": 403,
838 "message": "Rate Limit Exceeded"
839 }
840}"""
841
842
843NOT_CONFIGURED_RESPONSE = """{
844 "error": {
845 "errors": [
846 {
847 "domain": "usageLimits",
848 "reason": "accessNotConfigured",
849 "message": "Access Not Configured"
850 }
851 ],
852 "code": 403,
853 "message": "Access Not Configured"
854 }
855}"""
856
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800857LIST_NOT_CONFIGURED_RESPONSE = """[
858 "error": {
859 "errors": [
860 {
861 "domain": "usageLimits",
862 "reason": "accessNotConfigured",
863 "message": "Access Not Configured"
864 }
865 ],
866 "code": 403,
867 "message": "Access Not Configured"
868 }
869]"""
870
Joe Gregorio654f4a22012-02-09 14:15:44 -0500871
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700872class Callbacks(object):
873 def __init__(self):
874 self.responses = {}
875 self.exceptions = {}
876
877 def f(self, request_id, response, exception):
878 self.responses[request_id] = response
879 self.exceptions[request_id] = exception
Joe Gregorio654f4a22012-02-09 14:15:44 -0500880
881
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500882class TestHttpRequest(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700883 def test_unicode(self):
884 http = HttpMock(datafile("zoo.json"), headers={"status": "200"})
885 model = JsonModel()
886 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
887 method = u"POST"
888 request = HttpRequest(
889 http,
890 model.response,
891 uri,
892 method=method,
893 body=u"{}",
894 headers={"content-type": "application/json"},
895 )
896 request.execute()
897 self.assertEqual(uri, http.uri)
898 self.assertEqual(str, type(http.uri))
899 self.assertEqual(method, http.method)
900 self.assertEqual(str, type(http.method))
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500901
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700902 def test_empty_content_type(self):
903 """Test for #284"""
904 http = HttpMock(None, headers={"status": 200})
905 uri = u"https://www.googleapis.com/someapi/v1/upload/?foo=bar"
906 method = u"POST"
907 request = HttpRequest(
908 http, _postproc_none, uri, method=method, headers={"content-type": ""}
909 )
910 request.execute()
911 self.assertEqual("", http.headers.get("content-type"))
Xiaofei Wang20b67582019-07-17 11:16:53 -0700912
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700913 def test_no_retry_connection_errors(self):
914 model = JsonModel()
915 request = HttpRequest(
916 HttpMockWithNonRetriableErrors(1, {"status": "200"}, '{"foo": "bar"}'),
917 model.response,
918 u"https://www.example.com/json_api_endpoint",
919 )
920 request._sleep = lambda _x: 0 # do nothing
921 request._rand = lambda: 10
922 with self.assertRaises(socket.error):
923 response = request.execute(num_retries=3)
eesheeshc6425a02016-02-12 15:07:06 +0000924
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700925 def test_retry_connection_errors_non_resumable(self):
926 model = JsonModel()
927 request = HttpRequest(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100928 HttpMockWithErrors(5, {"status": "200"}, '{"foo": "bar"}'),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700929 model.response,
930 u"https://www.example.com/json_api_endpoint",
931 )
932 request._sleep = lambda _x: 0 # do nothing
933 request._rand = lambda: 10
Damian Gadomskic7516a22020-03-23 20:39:21 +0100934 response = request.execute(num_retries=5)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700935 self.assertEqual({u"foo": u"bar"}, response)
eesheeshc6425a02016-02-12 15:07:06 +0000936
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700937 def test_retry_connection_errors_resumable(self):
938 with open(datafile("small.png"), "rb") as small_png_file:
939 small_png_fd = BytesIO(small_png_file.read())
940 upload = MediaIoBaseUpload(
941 fd=small_png_fd, mimetype="image/png", chunksize=500, resumable=True
942 )
943 model = JsonModel()
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100944
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700945 request = HttpRequest(
946 HttpMockWithErrors(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100947 5, {"status": "200", "location": "location"}, '{"foo": "bar"}'
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700948 ),
949 model.response,
950 u"https://www.example.com/file_upload",
951 method="POST",
952 resumable=upload,
953 )
954 request._sleep = lambda _x: 0 # do nothing
955 request._rand = lambda: 10
Damian Gadomskic7516a22020-03-23 20:39:21 +0100956 response = request.execute(num_retries=5)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700957 self.assertEqual({u"foo": u"bar"}, response)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100958
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700959 def test_retry(self):
960 num_retries = 5
961 resp_seq = [({"status": "500"}, "")] * (num_retries - 3)
962 resp_seq.append(({"status": "403"}, RATE_LIMIT_EXCEEDED_RESPONSE))
963 resp_seq.append(({"status": "403"}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
964 resp_seq.append(({"status": "429"}, ""))
965 resp_seq.append(({"status": "200"}, "{}"))
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100966
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700967 http = HttpMockSequence(resp_seq)
968 model = JsonModel()
969 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
970 method = u"POST"
971 request = HttpRequest(
972 http,
973 model.response,
974 uri,
975 method=method,
976 body=u"{}",
977 headers={"content-type": "application/json"},
978 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400979
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700980 sleeptimes = []
981 request._sleep = lambda x: sleeptimes.append(x)
982 request._rand = lambda: 10
Joe Gregorio9086bd32013-06-14 16:32:05 -0400983
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700984 request.execute(num_retries=num_retries)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400985
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700986 self.assertEqual(num_retries, len(sleeptimes))
987 for retry_num in range(num_retries):
988 self.assertEqual(10 * 2 ** (retry_num + 1), sleeptimes[retry_num])
Joe Gregorio9086bd32013-06-14 16:32:05 -0400989
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700990 def test_no_retry_succeeds(self):
991 num_retries = 5
992 resp_seq = [({"status": "200"}, "{}")] * (num_retries)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400993
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700994 http = HttpMockSequence(resp_seq)
995 model = JsonModel()
996 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
997 method = u"POST"
998 request = HttpRequest(
999 http,
1000 model.response,
1001 uri,
1002 method=method,
1003 body=u"{}",
1004 headers={"content-type": "application/json"},
1005 )
eesheeshc6425a02016-02-12 15:07:06 +00001006
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001007 sleeptimes = []
1008 request._sleep = lambda x: sleeptimes.append(x)
1009 request._rand = lambda: 10
eesheeshc6425a02016-02-12 15:07:06 +00001010
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001011 request.execute(num_retries=num_retries)
eesheeshc6425a02016-02-12 15:07:06 +00001012
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001013 self.assertEqual(0, len(sleeptimes))
eesheeshc6425a02016-02-12 15:07:06 +00001014
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001015 def test_no_retry_fails_fast(self):
1016 http = HttpMockSequence([({"status": "500"}, ""), ({"status": "200"}, "{}")])
1017 model = JsonModel()
1018 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1019 method = u"POST"
1020 request = HttpRequest(
1021 http,
1022 model.response,
1023 uri,
1024 method=method,
1025 body=u"{}",
1026 headers={"content-type": "application/json"},
1027 )
eesheeshc6425a02016-02-12 15:07:06 +00001028
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001029 request._rand = lambda: 1.0
1030 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -04001031
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001032 with self.assertRaises(HttpError):
1033 request.execute()
1034 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -04001035
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001036 def test_no_retry_403_not_configured_fails_fast(self):
1037 http = HttpMockSequence(
1038 [({"status": "403"}, NOT_CONFIGURED_RESPONSE), ({"status": "200"}, "{}")]
1039 )
1040 model = JsonModel()
1041 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1042 method = u"POST"
1043 request = HttpRequest(
1044 http,
1045 model.response,
1046 uri,
1047 method=method,
1048 body=u"{}",
1049 headers={"content-type": "application/json"},
1050 )
Joe Gregorio9086bd32013-06-14 16:32:05 -04001051
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001052 request._rand = lambda: 1.0
1053 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001054
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001055 with self.assertRaises(HttpError):
1056 request.execute()
1057 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001058
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001059 def test_no_retry_403_fails_fast(self):
1060 http = HttpMockSequence([({"status": "403"}, ""), ({"status": "200"}, "{}")])
1061 model = JsonModel()
1062 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1063 method = u"POST"
1064 request = HttpRequest(
1065 http,
1066 model.response,
1067 uri,
1068 method=method,
1069 body=u"{}",
1070 headers={"content-type": "application/json"},
1071 )
eesheeshc6425a02016-02-12 15:07:06 +00001072
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001073 request._rand = lambda: 1.0
1074 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001075
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001076 with self.assertRaises(HttpError):
1077 request.execute()
1078 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001079
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001080 def test_no_retry_401_fails_fast(self):
1081 http = HttpMockSequence([({"status": "401"}, ""), ({"status": "200"}, "{}")])
1082 model = JsonModel()
1083 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1084 method = u"POST"
1085 request = HttpRequest(
1086 http,
1087 model.response,
1088 uri,
1089 method=method,
1090 body=u"{}",
1091 headers={"content-type": "application/json"},
1092 )
eesheeshc6425a02016-02-12 15:07:06 +00001093
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001094 request._rand = lambda: 1.0
1095 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001096
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001097 with self.assertRaises(HttpError):
1098 request.execute()
1099 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001100
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001101 def test_no_retry_403_list_fails(self):
1102 http = HttpMockSequence(
1103 [
1104 ({"status": "403"}, LIST_NOT_CONFIGURED_RESPONSE),
1105 ({"status": "200"}, "{}"),
1106 ]
1107 )
1108 model = JsonModel()
1109 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1110 method = u"POST"
1111 request = HttpRequest(
1112 http,
1113 model.response,
1114 uri,
1115 method=method,
1116 body=u"{}",
1117 headers={"content-type": "application/json"},
1118 )
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001119
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001120 request._rand = lambda: 1.0
1121 request._sleep = mock.MagicMock()
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001122
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001123 with self.assertRaises(HttpError):
1124 request.execute()
1125 request._sleep.assert_not_called()
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001126
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001127 def test_null_postproc(self):
1128 resp, content = HttpRequest.null_postproc("foo", "bar")
1129 self.assertEqual(resp, "foo")
1130 self.assertEqual(content, "bar")
1131
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001132
Joe Gregorio66f57522011-11-30 11:00:00 -05001133class TestBatch(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001134 def setUp(self):
1135 model = JsonModel()
1136 self.request1 = HttpRequest(
1137 None,
1138 model.response,
1139 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1140 method="POST",
1141 body="{}",
1142 headers={"content-type": "application/json"},
1143 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001144
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001145 self.request2 = HttpRequest(
1146 None,
1147 model.response,
1148 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1149 method="GET",
1150 body="",
1151 headers={"content-type": "application/json"},
1152 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001153
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001154 def test_id_to_from_content_id_header(self):
1155 batch = BatchHttpRequest()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001156 self.assertEqual("12", batch._header_to_id(batch._id_to_header("12")))
Joe Gregorio66f57522011-11-30 11:00:00 -05001157
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001158 def test_invalid_content_id_header(self):
1159 batch = BatchHttpRequest()
1160 self.assertRaises(BatchError, batch._header_to_id, "[foo+x]")
1161 self.assertRaises(BatchError, batch._header_to_id, "foo+1")
1162 self.assertRaises(BatchError, batch._header_to_id, "<foo>")
Joe Gregorio66f57522011-11-30 11:00:00 -05001163
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001164 def test_serialize_request(self):
1165 batch = BatchHttpRequest()
1166 request = HttpRequest(
1167 None,
1168 None,
1169 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1170 method="POST",
1171 body=u"{}",
1172 headers={"content-type": "application/json"},
1173 methodId=None,
1174 resumable=None,
1175 )
1176 s = batch._serialize_request(request).splitlines()
1177 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001178
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001179 def test_serialize_request_media_body(self):
1180 batch = BatchHttpRequest()
1181 f = open(datafile("small.png"), "rb")
1182 body = f.read()
1183 f.close()
Joe Gregorio66f57522011-11-30 11:00:00 -05001184
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001185 request = HttpRequest(
1186 None,
1187 None,
1188 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1189 method="POST",
1190 body=body,
1191 headers={"content-type": "application/json"},
1192 methodId=None,
1193 resumable=None,
1194 )
1195 # Just testing it shouldn't raise an exception.
1196 s = batch._serialize_request(request).splitlines()
Joe Gregorio66f57522011-11-30 11:00:00 -05001197
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001198 def test_serialize_request_no_body(self):
1199 batch = BatchHttpRequest()
1200 request = HttpRequest(
1201 None,
1202 None,
1203 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1204 method="POST",
1205 body=b"",
1206 headers={"content-type": "application/json"},
1207 methodId=None,
1208 resumable=None,
1209 )
1210 s = batch._serialize_request(request).splitlines()
1211 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001212
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001213 def test_serialize_get_request_no_body(self):
1214 batch = BatchHttpRequest()
1215 request = HttpRequest(
1216 None,
1217 None,
1218 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1219 method="GET",
1220 body=None,
1221 headers={"content-type": "application/json"},
1222 methodId=None,
1223 resumable=None,
1224 )
1225 s = batch._serialize_request(request).splitlines()
1226 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001227
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001228 def test_deserialize_response(self):
1229 batch = BatchHttpRequest()
1230 resp, content = batch._deserialize_response(RESPONSE)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001231
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001232 self.assertEqual(200, resp.status)
1233 self.assertEqual("OK", resp.reason)
1234 self.assertEqual(11, resp.version)
1235 self.assertEqual('{"answer": 42}', content)
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001236
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001237 def test_new_id(self):
1238 batch = BatchHttpRequest()
Joe Gregorio66f57522011-11-30 11:00:00 -05001239
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001240 id_ = batch._new_id()
1241 self.assertEqual("1", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001242
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001243 id_ = batch._new_id()
1244 self.assertEqual("2", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001245
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001246 batch.add(self.request1, request_id="3")
Joe Gregorio66f57522011-11-30 11:00:00 -05001247
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001248 id_ = batch._new_id()
1249 self.assertEqual("4", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001250
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001251 def test_add(self):
1252 batch = BatchHttpRequest()
1253 batch.add(self.request1, request_id="1")
1254 self.assertRaises(KeyError, batch.add, self.request1, request_id="1")
Joe Gregorio66f57522011-11-30 11:00:00 -05001255
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001256 def test_add_fail_for_over_limit(self):
1257 from googleapiclient.http import MAX_BATCH_LIMIT
Joe Gregorio66f57522011-11-30 11:00:00 -05001258
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001259 batch = BatchHttpRequest()
1260 for i in range(0, MAX_BATCH_LIMIT):
1261 batch.add(
1262 HttpRequest(
1263 None,
1264 None,
1265 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1266 method="POST",
1267 body="{}",
1268 headers={"content-type": "application/json"},
1269 )
Chris McDonough3cf5e602018-07-18 16:18:38 -04001270 )
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001271 self.assertRaises(BatchError, batch.add, self.request1)
Chris McDonough3cf5e602018-07-18 16:18:38 -04001272
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001273 def test_add_fail_for_resumable(self):
1274 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001275
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001276 upload = MediaFileUpload(datafile("small.png"), chunksize=500, resumable=True)
1277 self.request1.resumable = upload
1278 with self.assertRaises(BatchError) as batch_error:
1279 batch.add(self.request1, request_id="1")
1280 str(batch_error.exception)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001281
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001282 def test_execute_empty_batch_no_http(self):
1283 batch = BatchHttpRequest()
1284 ret = batch.execute()
1285 self.assertEqual(None, ret)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001286
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001287 def test_execute(self):
1288 batch = BatchHttpRequest()
1289 callbacks = Callbacks()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001290
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001291 batch.add(self.request1, callback=callbacks.f)
1292 batch.add(self.request2, callback=callbacks.f)
1293 http = HttpMockSequence(
1294 [
1295 (
1296 {
1297 "status": "200",
1298 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1299 },
1300 BATCH_RESPONSE,
1301 )
1302 ]
1303 )
1304 batch.execute(http=http)
1305 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1306 self.assertEqual(None, callbacks.exceptions["1"])
1307 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1308 self.assertEqual(None, callbacks.exceptions["2"])
Gabriel Garcia23174be2016-05-25 17:28:07 +02001309
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001310 def test_execute_request_body(self):
1311 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001312
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001313 batch.add(self.request1)
1314 batch.add(self.request2)
1315 http = HttpMockSequence(
1316 [
1317 (
1318 {
1319 "status": "200",
1320 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1321 },
1322 "echo_request_body",
1323 )
1324 ]
1325 )
1326 try:
1327 batch.execute(http=http)
1328 self.fail("Should raise exception")
1329 except BatchError as e:
1330 boundary, _ = e.content.split(None, 1)
1331 self.assertEqual("--", boundary[:2])
1332 parts = e.content.split(boundary)
1333 self.assertEqual(4, len(parts))
1334 self.assertEqual("", parts[0])
1335 self.assertEqual("--", parts[3].rstrip())
1336 header = parts[1].splitlines()[1]
1337 self.assertEqual("Content-Type: application/http", header)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001338
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001339 def test_execute_request_body_with_custom_long_request_ids(self):
1340 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001341
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001342 batch.add(self.request1, request_id="abc" * 20)
1343 batch.add(self.request2, request_id="def" * 20)
1344 http = HttpMockSequence(
1345 [
1346 (
1347 {
1348 "status": "200",
1349 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1350 },
1351 "echo_request_body",
1352 )
1353 ]
1354 )
1355 try:
1356 batch.execute(http=http)
1357 self.fail("Should raise exception")
1358 except BatchError as e:
1359 boundary, _ = e.content.split(None, 1)
1360 self.assertEqual("--", boundary[:2])
1361 parts = e.content.split(boundary)
1362 self.assertEqual(4, len(parts))
1363 self.assertEqual("", parts[0])
1364 self.assertEqual("--", parts[3].rstrip())
1365 for partindex, request_id in ((1, "abc" * 20), (2, "def" * 20)):
1366 lines = parts[partindex].splitlines()
1367 for n, line in enumerate(lines):
1368 if line.startswith("Content-ID:"):
1369 # assert correct header folding
1370 self.assertTrue(line.endswith("+"), line)
1371 header_continuation = lines[n + 1]
1372 self.assertEqual(
1373 header_continuation,
1374 " %s>" % request_id,
1375 header_continuation,
1376 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001377
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001378 def test_execute_initial_refresh_oauth2(self):
1379 batch = BatchHttpRequest()
1380 callbacks = Callbacks()
1381 cred = MockCredentials("Foo", expired=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001382
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001383 http = HttpMockSequence(
1384 [
1385 (
1386 {
1387 "status": "200",
1388 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1389 },
1390 BATCH_SINGLE_RESPONSE,
1391 )
1392 ]
1393 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001394
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001395 cred.authorize(http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001396
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001397 batch.add(self.request1, callback=callbacks.f)
1398 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001399
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001400 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1401 self.assertIsNone(callbacks.exceptions["1"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001402
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001403 self.assertEqual(1, cred._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001404
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001405 self.assertEqual(1, cred._authorized)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001406
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001407 self.assertEqual(1, cred._applied)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001408
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001409 def test_execute_refresh_and_retry_on_401(self):
1410 batch = BatchHttpRequest()
1411 callbacks = Callbacks()
1412 cred_1 = MockCredentials("Foo")
1413 cred_2 = MockCredentials("Bar")
Joe Gregorio654f4a22012-02-09 14:15:44 -05001414
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001415 http = HttpMockSequence(
1416 [
1417 (
1418 {
1419 "status": "200",
1420 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1421 },
1422 BATCH_RESPONSE_WITH_401,
1423 ),
1424 (
1425 {
1426 "status": "200",
1427 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1428 },
1429 BATCH_SINGLE_RESPONSE,
1430 ),
1431 ]
1432 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001433
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001434 creds_http_1 = HttpMockSequence([])
1435 cred_1.authorize(creds_http_1)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001436
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001437 creds_http_2 = HttpMockSequence([])
1438 cred_2.authorize(creds_http_2)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001439
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001440 self.request1.http = creds_http_1
1441 self.request2.http = creds_http_2
Joe Gregorio654f4a22012-02-09 14:15:44 -05001442
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001443 batch.add(self.request1, callback=callbacks.f)
1444 batch.add(self.request2, callback=callbacks.f)
1445 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001446
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001447 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1448 self.assertEqual(None, callbacks.exceptions["1"])
1449 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1450 self.assertEqual(None, callbacks.exceptions["2"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001451
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001452 self.assertEqual(1, cred_1._refreshed)
1453 self.assertEqual(0, cred_2._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001454
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001455 self.assertEqual(1, cred_1._authorized)
1456 self.assertEqual(1, cred_2._authorized)
Joe Gregorio66f57522011-11-30 11:00:00 -05001457
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001458 self.assertEqual(1, cred_2._applied)
1459 self.assertEqual(2, cred_1._applied)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001460
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001461 def test_http_errors_passed_to_callback(self):
1462 batch = BatchHttpRequest()
1463 callbacks = Callbacks()
1464 cred_1 = MockCredentials("Foo")
1465 cred_2 = MockCredentials("Bar")
Joe Gregorio3fb93672012-07-25 11:31:11 -04001466
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001467 http = HttpMockSequence(
1468 [
1469 (
1470 {
1471 "status": "200",
1472 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1473 },
1474 BATCH_RESPONSE_WITH_401,
1475 ),
1476 (
1477 {
1478 "status": "200",
1479 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1480 },
1481 BATCH_RESPONSE_WITH_401,
1482 ),
1483 ]
1484 )
1485
1486 creds_http_1 = HttpMockSequence([])
1487 cred_1.authorize(creds_http_1)
1488
1489 creds_http_2 = HttpMockSequence([])
1490 cred_2.authorize(creds_http_2)
1491
1492 self.request1.http = creds_http_1
1493 self.request2.http = creds_http_2
1494
1495 batch.add(self.request1, callback=callbacks.f)
1496 batch.add(self.request2, callback=callbacks.f)
1497 batch.execute(http=http)
1498
1499 self.assertEqual(None, callbacks.responses["1"])
1500 self.assertEqual(401, callbacks.exceptions["1"].resp.status)
1501 self.assertEqual(
1502 "Authorization Required", callbacks.exceptions["1"].resp.reason
1503 )
1504 self.assertEqual({u"baz": u"qux"}, callbacks.responses["2"])
1505 self.assertEqual(None, callbacks.exceptions["2"])
1506
1507 def test_execute_global_callback(self):
1508 callbacks = Callbacks()
1509 batch = BatchHttpRequest(callback=callbacks.f)
1510
1511 batch.add(self.request1)
1512 batch.add(self.request2)
1513 http = HttpMockSequence(
1514 [
1515 (
1516 {
1517 "status": "200",
1518 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1519 },
1520 BATCH_RESPONSE,
1521 )
1522 ]
1523 )
1524 batch.execute(http=http)
1525 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1526 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1527
1528 def test_execute_batch_http_error(self):
1529 callbacks = Callbacks()
1530 batch = BatchHttpRequest(callback=callbacks.f)
1531
1532 batch.add(self.request1)
1533 batch.add(self.request2)
1534 http = HttpMockSequence(
1535 [
1536 (
1537 {
1538 "status": "200",
1539 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1540 },
1541 BATCH_ERROR_RESPONSE,
1542 )
1543 ]
1544 )
1545 batch.execute(http=http)
1546 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1547 expected = (
1548 "<HttpError 403 when requesting "
1549 "https://www.googleapis.com/someapi/v1/collection/?foo=bar returned "
1550 '"Access Not Configured">'
1551 )
1552 self.assertEqual(expected, str(callbacks.exceptions["2"]))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001553
Joe Gregorio5c120db2012-08-23 09:13:55 -04001554
Joe Gregorioba5c7902012-08-03 12:48:16 -04001555class TestRequestUriTooLong(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001556 def test_turn_get_into_post(self):
1557 def _postproc(resp, content):
1558 return content
Joe Gregorioba5c7902012-08-03 12:48:16 -04001559
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001560 http = HttpMockSequence(
1561 [
1562 ({"status": "200"}, "echo_request_body"),
1563 ({"status": "200"}, "echo_request_headers"),
1564 ]
1565 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001566
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001567 # Send a long query parameter.
1568 query = {"q": "a" * MAX_URI_LENGTH + "?&"}
1569 req = HttpRequest(
1570 http,
1571 _postproc,
1572 "http://example.com?" + urlencode(query),
1573 method="GET",
1574 body=None,
1575 headers={},
1576 methodId="foo",
1577 resumable=None,
1578 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001579
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001580 # Query parameters should be sent in the body.
1581 response = req.execute()
1582 self.assertEqual(b"q=" + b"a" * MAX_URI_LENGTH + b"%3F%26", response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001583
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001584 # Extra headers should be set.
1585 response = req.execute()
1586 self.assertEqual("GET", response["x-http-method-override"])
1587 self.assertEqual(str(MAX_URI_LENGTH + 8), response["content-length"])
1588 self.assertEqual("application/x-www-form-urlencoded", response["content-type"])
Joe Gregorioba5c7902012-08-03 12:48:16 -04001589
Joe Gregorio5c120db2012-08-23 09:13:55 -04001590
1591class TestStreamSlice(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001592 """Test _StreamSlice."""
Joe Gregorio5c120db2012-08-23 09:13:55 -04001593
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001594 def setUp(self):
1595 self.stream = BytesIO(b"0123456789")
Joe Gregorio5c120db2012-08-23 09:13:55 -04001596
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001597 def test_read(self):
1598 s = _StreamSlice(self.stream, 0, 4)
1599 self.assertEqual(b"", s.read(0))
1600 self.assertEqual(b"0", s.read(1))
1601 self.assertEqual(b"123", s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001602
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001603 def test_read_too_much(self):
1604 s = _StreamSlice(self.stream, 1, 4)
1605 self.assertEqual(b"1234", s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001606
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001607 def test_read_all(self):
1608 s = _StreamSlice(self.stream, 2, 1)
1609 self.assertEqual(b"2", s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001610
Ali Afshar164f37e2013-01-07 14:05:45 -08001611
1612class TestResponseCallback(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001613 """Test adding callbacks to responses."""
Ali Afshar164f37e2013-01-07 14:05:45 -08001614
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001615 def test_ensure_response_callback(self):
1616 m = JsonModel()
1617 request = HttpRequest(
1618 None,
1619 m.response,
1620 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1621 method="POST",
1622 body="{}",
1623 headers={"content-type": "application/json"},
1624 )
1625 h = HttpMockSequence([({"status": 200}, "{}")])
1626 responses = []
1627
1628 def _on_response(resp, responses=responses):
1629 responses.append(resp)
1630
1631 request.add_response_callback(_on_response)
1632 request.execute(http=h)
1633 self.assertEqual(1, len(responses))
Ali Afshar164f37e2013-01-07 14:05:45 -08001634
1635
Craig Gurnik8e55b762015-01-20 15:00:10 -05001636class TestHttpMock(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001637 def test_default_response_headers(self):
1638 http = HttpMock(datafile("zoo.json"))
1639 resp, content = http.request("http://example.com")
1640 self.assertEqual(resp.status, 200)
Craig Gurnik8e55b762015-01-20 15:00:10 -05001641
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001642 def test_error_response(self):
1643 http = HttpMock(datafile("bad_request.json"), {"status": "400"})
1644 model = JsonModel()
1645 request = HttpRequest(
1646 http,
1647 model.response,
1648 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1649 method="GET",
1650 headers={},
1651 )
1652 self.assertRaises(HttpError, request.execute)
Alan Briolat26b01002015-08-14 00:13:57 +01001653
Craig Gurnik8e55b762015-01-20 15:00:10 -05001654
Igor Maravić22435292017-01-19 22:28:22 +01001655class TestHttpBuild(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001656 original_socket_default_timeout = None
Igor Maravić22435292017-01-19 22:28:22 +01001657
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001658 @classmethod
1659 def setUpClass(cls):
1660 cls.original_socket_default_timeout = socket.getdefaulttimeout()
Igor Maravić22435292017-01-19 22:28:22 +01001661
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001662 @classmethod
1663 def tearDownClass(cls):
1664 socket.setdefaulttimeout(cls.original_socket_default_timeout)
Igor Maravić22435292017-01-19 22:28:22 +01001665
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001666 def test_build_http_sets_default_timeout_if_none_specified(self):
1667 socket.setdefaulttimeout(None)
1668 http = build_http()
1669 self.assertIsInstance(http.timeout, int)
1670 self.assertGreater(http.timeout, 0)
Igor Maravić22435292017-01-19 22:28:22 +01001671
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001672 def test_build_http_default_timeout_can_be_overridden(self):
1673 socket.setdefaulttimeout(1.5)
1674 http = build_http()
1675 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
Igor Maravić22435292017-01-19 22:28:22 +01001676
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001677 def test_build_http_default_timeout_can_be_set_to_zero(self):
1678 socket.setdefaulttimeout(0)
1679 http = build_http()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001680 self.assertEqual(http.timeout, 0)
Bu Sun Kim790e7022020-09-11 20:18:06 -06001681
Bu Sun Kimb3b773f2020-03-11 12:58:16 -07001682 def test_build_http_default_308_is_excluded_as_redirect(self):
1683 http = build_http()
1684 self.assertTrue(308 not in http.redirect_codes)
Igor Maravić22435292017-01-19 22:28:22 +01001685
1686
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001687if __name__ == "__main__":
1688 logging.getLogger().setLevel(logging.ERROR)
1689 unittest.main()