blob: 5293a6a3bfc05d96fc935c3cd1c047cc8b4655ef [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 Kim86d87882020-10-22 08:51:16 -0600675 def test_media_io_base_download_empty_file_416_response(self):
676 self.request.http = HttpMockSequence(
677 [({"status": "416", "content-range": "0-0/0"}, b"")]
678 )
679
680 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
681
682 self.assertEqual(self.fd, download._fd)
683 self.assertEqual(0, download._progress)
684 self.assertEqual(None, download._total_size)
685 self.assertEqual(False, download._done)
686 self.assertEqual(self.request.uri, download._uri)
687
688 status, done = download.next_chunk()
689
690 self.assertEqual(True, done)
691 self.assertEqual(0, download._progress)
692 self.assertEqual(0, download._total_size)
693 self.assertEqual(0, status.progress())
694
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700695 def test_media_io_base_download_unknown_media_size(self):
696 self.request.http = HttpMockSequence([({"status": "200"}, b"123")])
andrewnestera4a44cf2017-03-31 16:09:31 +0300697
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700698 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Daniel44067782018-01-16 23:17:56 +0100699
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700700 self.assertEqual(self.fd, download._fd)
701 self.assertEqual(0, download._progress)
702 self.assertEqual(None, download._total_size)
703 self.assertEqual(False, download._done)
704 self.assertEqual(self.request.uri, download._uri)
Daniel44067782018-01-16 23:17:56 +0100705
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700706 status, done = download.next_chunk()
Daniel44067782018-01-16 23:17:56 +0100707
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700708 self.assertEqual(self.fd.getvalue(), b"123")
709 self.assertEqual(True, done)
710 self.assertEqual(3, download._progress)
711 self.assertEqual(None, download._total_size)
712 self.assertEqual(0, status.progress())
Daniel44067782018-01-16 23:17:56 +0100713
714
Joe Gregorio66f57522011-11-30 11:00:00 -0500715EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
716Content-Type: application/json
717MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500718Host: www.googleapis.com
719content-length: 2\r\n\r\n{}"""
720
721
722NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
723Content-Type: application/json
724MIME-Version: 1.0
725Host: www.googleapis.com
726content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500727
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400728NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
729Content-Type: application/json
730MIME-Version: 1.0
731Host: www.googleapis.com\r\n\r\n"""
732
Joe Gregorio66f57522011-11-30 11:00:00 -0500733
734RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400735Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500736Content-Length: 14
737ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
738
739
INADA Naoki09157612015-03-25 01:51:03 +0900740BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500741Content-Type: application/http
742Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400743Content-ID: <randomness + 1>
Joe Gregorio66f57522011-11-30 11:00:00 -0500744
745HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400746Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500747Content-Length: 14
748ETag: "etag/pony"\r\n\r\n{"foo": 42}
749
750--batch_foobarbaz
751Content-Type: application/http
752Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400753Content-ID: <randomness + 2>
Joe Gregorio66f57522011-11-30 11:00:00 -0500754
755HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400756Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500757Content-Length: 14
758ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
759--batch_foobarbaz--"""
760
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500761
INADA Naoki09157612015-03-25 01:51:03 +0900762BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400763Content-Type: application/http
764Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400765Content-ID: <randomness + 1>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400766
767HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400768Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400769Content-Length: 14
770ETag: "etag/pony"\r\n\r\n{"foo": 42}
771
772--batch_foobarbaz
773Content-Type: application/http
774Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400775Content-ID: <randomness + 2>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400776
777HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400778Content-Type: application/json
779Content-Length: 245
780ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400781 "error": {
782 "errors": [
783 {
784 "domain": "usageLimits",
785 "reason": "accessNotConfigured",
786 "message": "Access Not Configured",
787 "debugInfo": "QuotaState: BLOCKED"
788 }
789 ],
790 "code": 403,
791 "message": "Access Not Configured"
792 }
793}
794
795--batch_foobarbaz--"""
796
797
INADA Naoki09157612015-03-25 01:51:03 +0900798BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500799Content-Type: application/http
800Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400801Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500802
Joe Gregorioc752e332012-07-11 14:43:52 -0400803HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400804Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500805Content-Length: 14
806ETag: "etag/pony"\r\n\r\n{"error": {"message":
807 "Authorizaton failed."}}
808
809--batch_foobarbaz
810Content-Type: application/http
811Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400812Content-ID: <randomness + 2>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500813
814HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400815Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500816Content-Length: 14
817ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
818--batch_foobarbaz--"""
819
820
INADA Naoki09157612015-03-25 01:51:03 +0900821BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500822Content-Type: application/http
823Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400824Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500825
826HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400827Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500828Content-Length: 14
829ETag: "etag/pony"\r\n\r\n{"foo": 42}
830--batch_foobarbaz--"""
831
eesheeshc6425a02016-02-12 15:07:06 +0000832
833USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
834 "error": {
835 "errors": [
836 {
837 "domain": "usageLimits",
838 "reason": "userRateLimitExceeded",
839 "message": "User Rate Limit Exceeded"
840 }
841 ],
842 "code": 403,
843 "message": "User Rate Limit Exceeded"
844 }
845}"""
846
847
848RATE_LIMIT_EXCEEDED_RESPONSE = """{
849 "error": {
850 "errors": [
851 {
852 "domain": "usageLimits",
853 "reason": "rateLimitExceeded",
854 "message": "Rate Limit Exceeded"
855 }
856 ],
857 "code": 403,
858 "message": "Rate Limit Exceeded"
859 }
860}"""
861
862
863NOT_CONFIGURED_RESPONSE = """{
864 "error": {
865 "errors": [
866 {
867 "domain": "usageLimits",
868 "reason": "accessNotConfigured",
869 "message": "Access Not Configured"
870 }
871 ],
872 "code": 403,
873 "message": "Access Not Configured"
874 }
875}"""
876
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800877LIST_NOT_CONFIGURED_RESPONSE = """[
878 "error": {
879 "errors": [
880 {
881 "domain": "usageLimits",
882 "reason": "accessNotConfigured",
883 "message": "Access Not Configured"
884 }
885 ],
886 "code": 403,
887 "message": "Access Not Configured"
888 }
889]"""
890
Joe Gregorio654f4a22012-02-09 14:15:44 -0500891
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700892class Callbacks(object):
893 def __init__(self):
894 self.responses = {}
895 self.exceptions = {}
896
897 def f(self, request_id, response, exception):
898 self.responses[request_id] = response
899 self.exceptions[request_id] = exception
Joe Gregorio654f4a22012-02-09 14:15:44 -0500900
901
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500902class TestHttpRequest(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700903 def test_unicode(self):
904 http = HttpMock(datafile("zoo.json"), headers={"status": "200"})
905 model = JsonModel()
906 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
907 method = u"POST"
908 request = HttpRequest(
909 http,
910 model.response,
911 uri,
912 method=method,
913 body=u"{}",
914 headers={"content-type": "application/json"},
915 )
916 request.execute()
917 self.assertEqual(uri, http.uri)
918 self.assertEqual(str, type(http.uri))
919 self.assertEqual(method, http.method)
920 self.assertEqual(str, type(http.method))
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500921
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700922 def test_empty_content_type(self):
923 """Test for #284"""
924 http = HttpMock(None, headers={"status": 200})
925 uri = u"https://www.googleapis.com/someapi/v1/upload/?foo=bar"
926 method = u"POST"
927 request = HttpRequest(
928 http, _postproc_none, uri, method=method, headers={"content-type": ""}
929 )
930 request.execute()
931 self.assertEqual("", http.headers.get("content-type"))
Xiaofei Wang20b67582019-07-17 11:16:53 -0700932
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700933 def test_no_retry_connection_errors(self):
934 model = JsonModel()
935 request = HttpRequest(
936 HttpMockWithNonRetriableErrors(1, {"status": "200"}, '{"foo": "bar"}'),
937 model.response,
938 u"https://www.example.com/json_api_endpoint",
939 )
940 request._sleep = lambda _x: 0 # do nothing
941 request._rand = lambda: 10
942 with self.assertRaises(socket.error):
943 response = request.execute(num_retries=3)
eesheeshc6425a02016-02-12 15:07:06 +0000944
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700945 def test_retry_connection_errors_non_resumable(self):
946 model = JsonModel()
947 request = HttpRequest(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100948 HttpMockWithErrors(5, {"status": "200"}, '{"foo": "bar"}'),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700949 model.response,
950 u"https://www.example.com/json_api_endpoint",
951 )
952 request._sleep = lambda _x: 0 # do nothing
953 request._rand = lambda: 10
Damian Gadomskic7516a22020-03-23 20:39:21 +0100954 response = request.execute(num_retries=5)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700955 self.assertEqual({u"foo": u"bar"}, response)
eesheeshc6425a02016-02-12 15:07:06 +0000956
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700957 def test_retry_connection_errors_resumable(self):
958 with open(datafile("small.png"), "rb") as small_png_file:
959 small_png_fd = BytesIO(small_png_file.read())
960 upload = MediaIoBaseUpload(
961 fd=small_png_fd, mimetype="image/png", chunksize=500, resumable=True
962 )
963 model = JsonModel()
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100964
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700965 request = HttpRequest(
966 HttpMockWithErrors(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100967 5, {"status": "200", "location": "location"}, '{"foo": "bar"}'
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700968 ),
969 model.response,
970 u"https://www.example.com/file_upload",
971 method="POST",
972 resumable=upload,
973 )
974 request._sleep = lambda _x: 0 # do nothing
975 request._rand = lambda: 10
Damian Gadomskic7516a22020-03-23 20:39:21 +0100976 response = request.execute(num_retries=5)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700977 self.assertEqual({u"foo": u"bar"}, response)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100978
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700979 def test_retry(self):
980 num_retries = 5
981 resp_seq = [({"status": "500"}, "")] * (num_retries - 3)
982 resp_seq.append(({"status": "403"}, RATE_LIMIT_EXCEEDED_RESPONSE))
983 resp_seq.append(({"status": "403"}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
984 resp_seq.append(({"status": "429"}, ""))
985 resp_seq.append(({"status": "200"}, "{}"))
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100986
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700987 http = HttpMockSequence(resp_seq)
988 model = JsonModel()
989 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
990 method = u"POST"
991 request = HttpRequest(
992 http,
993 model.response,
994 uri,
995 method=method,
996 body=u"{}",
997 headers={"content-type": "application/json"},
998 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400999
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001000 sleeptimes = []
1001 request._sleep = lambda x: sleeptimes.append(x)
1002 request._rand = lambda: 10
Joe Gregorio9086bd32013-06-14 16:32:05 -04001003
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001004 request.execute(num_retries=num_retries)
Joe Gregorio9086bd32013-06-14 16:32:05 -04001005
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001006 self.assertEqual(num_retries, len(sleeptimes))
1007 for retry_num in range(num_retries):
1008 self.assertEqual(10 * 2 ** (retry_num + 1), sleeptimes[retry_num])
Joe Gregorio9086bd32013-06-14 16:32:05 -04001009
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001010 def test_no_retry_succeeds(self):
1011 num_retries = 5
1012 resp_seq = [({"status": "200"}, "{}")] * (num_retries)
Joe Gregorio9086bd32013-06-14 16:32:05 -04001013
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001014 http = HttpMockSequence(resp_seq)
1015 model = JsonModel()
1016 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1017 method = u"POST"
1018 request = HttpRequest(
1019 http,
1020 model.response,
1021 uri,
1022 method=method,
1023 body=u"{}",
1024 headers={"content-type": "application/json"},
1025 )
eesheeshc6425a02016-02-12 15:07:06 +00001026
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001027 sleeptimes = []
1028 request._sleep = lambda x: sleeptimes.append(x)
1029 request._rand = lambda: 10
eesheeshc6425a02016-02-12 15:07:06 +00001030
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001031 request.execute(num_retries=num_retries)
eesheeshc6425a02016-02-12 15:07:06 +00001032
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001033 self.assertEqual(0, len(sleeptimes))
eesheeshc6425a02016-02-12 15:07:06 +00001034
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001035 def test_no_retry_fails_fast(self):
1036 http = HttpMockSequence([({"status": "500"}, ""), ({"status": "200"}, "{}")])
1037 model = JsonModel()
1038 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1039 method = u"POST"
1040 request = HttpRequest(
1041 http,
1042 model.response,
1043 uri,
1044 method=method,
1045 body=u"{}",
1046 headers={"content-type": "application/json"},
1047 )
eesheeshc6425a02016-02-12 15:07:06 +00001048
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001049 request._rand = lambda: 1.0
1050 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -04001051
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001052 with self.assertRaises(HttpError):
1053 request.execute()
1054 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -04001055
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001056 def test_no_retry_403_not_configured_fails_fast(self):
1057 http = HttpMockSequence(
1058 [({"status": "403"}, NOT_CONFIGURED_RESPONSE), ({"status": "200"}, "{}")]
1059 )
1060 model = JsonModel()
1061 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1062 method = u"POST"
1063 request = HttpRequest(
1064 http,
1065 model.response,
1066 uri,
1067 method=method,
1068 body=u"{}",
1069 headers={"content-type": "application/json"},
1070 )
Joe Gregorio9086bd32013-06-14 16:32:05 -04001071
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001072 request._rand = lambda: 1.0
1073 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001074
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001075 with self.assertRaises(HttpError):
1076 request.execute()
1077 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001078
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001079 def test_no_retry_403_fails_fast(self):
1080 http = HttpMockSequence([({"status": "403"}, ""), ({"status": "200"}, "{}")])
1081 model = JsonModel()
1082 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1083 method = u"POST"
1084 request = HttpRequest(
1085 http,
1086 model.response,
1087 uri,
1088 method=method,
1089 body=u"{}",
1090 headers={"content-type": "application/json"},
1091 )
eesheeshc6425a02016-02-12 15:07:06 +00001092
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001093 request._rand = lambda: 1.0
1094 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001095
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001096 with self.assertRaises(HttpError):
1097 request.execute()
1098 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001099
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001100 def test_no_retry_401_fails_fast(self):
1101 http = HttpMockSequence([({"status": "401"}, ""), ({"status": "200"}, "{}")])
1102 model = JsonModel()
1103 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1104 method = u"POST"
1105 request = HttpRequest(
1106 http,
1107 model.response,
1108 uri,
1109 method=method,
1110 body=u"{}",
1111 headers={"content-type": "application/json"},
1112 )
eesheeshc6425a02016-02-12 15:07:06 +00001113
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001114 request._rand = lambda: 1.0
1115 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001116
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001117 with self.assertRaises(HttpError):
1118 request.execute()
1119 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001120
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001121 def test_no_retry_403_list_fails(self):
1122 http = HttpMockSequence(
1123 [
1124 ({"status": "403"}, LIST_NOT_CONFIGURED_RESPONSE),
1125 ({"status": "200"}, "{}"),
1126 ]
1127 )
1128 model = JsonModel()
1129 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1130 method = u"POST"
1131 request = HttpRequest(
1132 http,
1133 model.response,
1134 uri,
1135 method=method,
1136 body=u"{}",
1137 headers={"content-type": "application/json"},
1138 )
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001139
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001140 request._rand = lambda: 1.0
1141 request._sleep = mock.MagicMock()
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001142
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001143 with self.assertRaises(HttpError):
1144 request.execute()
1145 request._sleep.assert_not_called()
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001146
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001147 def test_null_postproc(self):
1148 resp, content = HttpRequest.null_postproc("foo", "bar")
1149 self.assertEqual(resp, "foo")
1150 self.assertEqual(content, "bar")
1151
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001152
Joe Gregorio66f57522011-11-30 11:00:00 -05001153class TestBatch(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001154 def setUp(self):
1155 model = JsonModel()
1156 self.request1 = HttpRequest(
1157 None,
1158 model.response,
1159 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1160 method="POST",
1161 body="{}",
1162 headers={"content-type": "application/json"},
1163 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001164
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001165 self.request2 = HttpRequest(
1166 None,
1167 model.response,
1168 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1169 method="GET",
1170 body="",
1171 headers={"content-type": "application/json"},
1172 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001173
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001174 def test_id_to_from_content_id_header(self):
1175 batch = BatchHttpRequest()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001176 self.assertEqual("12", batch._header_to_id(batch._id_to_header("12")))
Joe Gregorio66f57522011-11-30 11:00:00 -05001177
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001178 def test_invalid_content_id_header(self):
1179 batch = BatchHttpRequest()
1180 self.assertRaises(BatchError, batch._header_to_id, "[foo+x]")
1181 self.assertRaises(BatchError, batch._header_to_id, "foo+1")
1182 self.assertRaises(BatchError, batch._header_to_id, "<foo>")
Joe Gregorio66f57522011-11-30 11:00:00 -05001183
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001184 def test_serialize_request(self):
1185 batch = BatchHttpRequest()
1186 request = HttpRequest(
1187 None,
1188 None,
1189 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1190 method="POST",
1191 body=u"{}",
1192 headers={"content-type": "application/json"},
1193 methodId=None,
1194 resumable=None,
1195 )
1196 s = batch._serialize_request(request).splitlines()
1197 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001198
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001199 def test_serialize_request_media_body(self):
1200 batch = BatchHttpRequest()
1201 f = open(datafile("small.png"), "rb")
1202 body = f.read()
1203 f.close()
Joe Gregorio66f57522011-11-30 11:00:00 -05001204
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001205 request = HttpRequest(
1206 None,
1207 None,
1208 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1209 method="POST",
1210 body=body,
1211 headers={"content-type": "application/json"},
1212 methodId=None,
1213 resumable=None,
1214 )
1215 # Just testing it shouldn't raise an exception.
1216 s = batch._serialize_request(request).splitlines()
Joe Gregorio66f57522011-11-30 11:00:00 -05001217
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001218 def test_serialize_request_no_body(self):
1219 batch = BatchHttpRequest()
1220 request = HttpRequest(
1221 None,
1222 None,
1223 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1224 method="POST",
1225 body=b"",
1226 headers={"content-type": "application/json"},
1227 methodId=None,
1228 resumable=None,
1229 )
1230 s = batch._serialize_request(request).splitlines()
1231 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001232
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001233 def test_serialize_get_request_no_body(self):
1234 batch = BatchHttpRequest()
1235 request = HttpRequest(
1236 None,
1237 None,
1238 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1239 method="GET",
1240 body=None,
1241 headers={"content-type": "application/json"},
1242 methodId=None,
1243 resumable=None,
1244 )
1245 s = batch._serialize_request(request).splitlines()
1246 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001247
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001248 def test_deserialize_response(self):
1249 batch = BatchHttpRequest()
1250 resp, content = batch._deserialize_response(RESPONSE)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001251
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001252 self.assertEqual(200, resp.status)
1253 self.assertEqual("OK", resp.reason)
1254 self.assertEqual(11, resp.version)
1255 self.assertEqual('{"answer": 42}', content)
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001256
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001257 def test_new_id(self):
1258 batch = BatchHttpRequest()
Joe Gregorio66f57522011-11-30 11:00:00 -05001259
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001260 id_ = batch._new_id()
1261 self.assertEqual("1", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001262
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001263 id_ = batch._new_id()
1264 self.assertEqual("2", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001265
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001266 batch.add(self.request1, request_id="3")
Joe Gregorio66f57522011-11-30 11:00:00 -05001267
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001268 id_ = batch._new_id()
1269 self.assertEqual("4", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001270
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001271 def test_add(self):
1272 batch = BatchHttpRequest()
1273 batch.add(self.request1, request_id="1")
1274 self.assertRaises(KeyError, batch.add, self.request1, request_id="1")
Joe Gregorio66f57522011-11-30 11:00:00 -05001275
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001276 def test_add_fail_for_over_limit(self):
1277 from googleapiclient.http import MAX_BATCH_LIMIT
Joe Gregorio66f57522011-11-30 11:00:00 -05001278
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001279 batch = BatchHttpRequest()
1280 for i in range(0, MAX_BATCH_LIMIT):
1281 batch.add(
1282 HttpRequest(
1283 None,
1284 None,
1285 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1286 method="POST",
1287 body="{}",
1288 headers={"content-type": "application/json"},
1289 )
Chris McDonough3cf5e602018-07-18 16:18:38 -04001290 )
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001291 self.assertRaises(BatchError, batch.add, self.request1)
Chris McDonough3cf5e602018-07-18 16:18:38 -04001292
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001293 def test_add_fail_for_resumable(self):
1294 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001295
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001296 upload = MediaFileUpload(datafile("small.png"), chunksize=500, resumable=True)
1297 self.request1.resumable = upload
1298 with self.assertRaises(BatchError) as batch_error:
1299 batch.add(self.request1, request_id="1")
1300 str(batch_error.exception)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001301
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001302 def test_execute_empty_batch_no_http(self):
1303 batch = BatchHttpRequest()
1304 ret = batch.execute()
1305 self.assertEqual(None, ret)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001306
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001307 def test_execute(self):
1308 batch = BatchHttpRequest()
1309 callbacks = Callbacks()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001310
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001311 batch.add(self.request1, callback=callbacks.f)
1312 batch.add(self.request2, callback=callbacks.f)
1313 http = HttpMockSequence(
1314 [
1315 (
1316 {
1317 "status": "200",
1318 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1319 },
1320 BATCH_RESPONSE,
1321 )
1322 ]
1323 )
1324 batch.execute(http=http)
1325 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1326 self.assertEqual(None, callbacks.exceptions["1"])
1327 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1328 self.assertEqual(None, callbacks.exceptions["2"])
Gabriel Garcia23174be2016-05-25 17:28:07 +02001329
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001330 def test_execute_request_body(self):
1331 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001332
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001333 batch.add(self.request1)
1334 batch.add(self.request2)
1335 http = HttpMockSequence(
1336 [
1337 (
1338 {
1339 "status": "200",
1340 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1341 },
1342 "echo_request_body",
1343 )
1344 ]
1345 )
1346 try:
1347 batch.execute(http=http)
1348 self.fail("Should raise exception")
1349 except BatchError as e:
1350 boundary, _ = e.content.split(None, 1)
1351 self.assertEqual("--", boundary[:2])
1352 parts = e.content.split(boundary)
1353 self.assertEqual(4, len(parts))
1354 self.assertEqual("", parts[0])
1355 self.assertEqual("--", parts[3].rstrip())
1356 header = parts[1].splitlines()[1]
1357 self.assertEqual("Content-Type: application/http", header)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001358
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001359 def test_execute_request_body_with_custom_long_request_ids(self):
1360 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001361
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001362 batch.add(self.request1, request_id="abc" * 20)
1363 batch.add(self.request2, request_id="def" * 20)
1364 http = HttpMockSequence(
1365 [
1366 (
1367 {
1368 "status": "200",
1369 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1370 },
1371 "echo_request_body",
1372 )
1373 ]
1374 )
1375 try:
1376 batch.execute(http=http)
1377 self.fail("Should raise exception")
1378 except BatchError as e:
1379 boundary, _ = e.content.split(None, 1)
1380 self.assertEqual("--", boundary[:2])
1381 parts = e.content.split(boundary)
1382 self.assertEqual(4, len(parts))
1383 self.assertEqual("", parts[0])
1384 self.assertEqual("--", parts[3].rstrip())
1385 for partindex, request_id in ((1, "abc" * 20), (2, "def" * 20)):
1386 lines = parts[partindex].splitlines()
1387 for n, line in enumerate(lines):
1388 if line.startswith("Content-ID:"):
1389 # assert correct header folding
1390 self.assertTrue(line.endswith("+"), line)
1391 header_continuation = lines[n + 1]
1392 self.assertEqual(
1393 header_continuation,
1394 " %s>" % request_id,
1395 header_continuation,
1396 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001397
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001398 def test_execute_initial_refresh_oauth2(self):
1399 batch = BatchHttpRequest()
1400 callbacks = Callbacks()
1401 cred = MockCredentials("Foo", expired=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001402
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001403 http = HttpMockSequence(
1404 [
1405 (
1406 {
1407 "status": "200",
1408 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1409 },
1410 BATCH_SINGLE_RESPONSE,
1411 )
1412 ]
1413 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001414
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001415 cred.authorize(http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001416
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001417 batch.add(self.request1, callback=callbacks.f)
1418 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001419
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001420 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1421 self.assertIsNone(callbacks.exceptions["1"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001422
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001423 self.assertEqual(1, cred._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001424
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001425 self.assertEqual(1, cred._authorized)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001426
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001427 self.assertEqual(1, cred._applied)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001428
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001429 def test_execute_refresh_and_retry_on_401(self):
1430 batch = BatchHttpRequest()
1431 callbacks = Callbacks()
1432 cred_1 = MockCredentials("Foo")
1433 cred_2 = MockCredentials("Bar")
Joe Gregorio654f4a22012-02-09 14:15:44 -05001434
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001435 http = HttpMockSequence(
1436 [
1437 (
1438 {
1439 "status": "200",
1440 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1441 },
1442 BATCH_RESPONSE_WITH_401,
1443 ),
1444 (
1445 {
1446 "status": "200",
1447 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1448 },
1449 BATCH_SINGLE_RESPONSE,
1450 ),
1451 ]
1452 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001453
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001454 creds_http_1 = HttpMockSequence([])
1455 cred_1.authorize(creds_http_1)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001456
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001457 creds_http_2 = HttpMockSequence([])
1458 cred_2.authorize(creds_http_2)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001459
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001460 self.request1.http = creds_http_1
1461 self.request2.http = creds_http_2
Joe Gregorio654f4a22012-02-09 14:15:44 -05001462
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001463 batch.add(self.request1, callback=callbacks.f)
1464 batch.add(self.request2, callback=callbacks.f)
1465 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001466
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001467 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1468 self.assertEqual(None, callbacks.exceptions["1"])
1469 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1470 self.assertEqual(None, callbacks.exceptions["2"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001471
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001472 self.assertEqual(1, cred_1._refreshed)
1473 self.assertEqual(0, cred_2._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001474
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001475 self.assertEqual(1, cred_1._authorized)
1476 self.assertEqual(1, cred_2._authorized)
Joe Gregorio66f57522011-11-30 11:00:00 -05001477
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001478 self.assertEqual(1, cred_2._applied)
1479 self.assertEqual(2, cred_1._applied)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001480
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001481 def test_http_errors_passed_to_callback(self):
1482 batch = BatchHttpRequest()
1483 callbacks = Callbacks()
1484 cred_1 = MockCredentials("Foo")
1485 cred_2 = MockCredentials("Bar")
Joe Gregorio3fb93672012-07-25 11:31:11 -04001486
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001487 http = HttpMockSequence(
1488 [
1489 (
1490 {
1491 "status": "200",
1492 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1493 },
1494 BATCH_RESPONSE_WITH_401,
1495 ),
1496 (
1497 {
1498 "status": "200",
1499 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1500 },
1501 BATCH_RESPONSE_WITH_401,
1502 ),
1503 ]
1504 )
1505
1506 creds_http_1 = HttpMockSequence([])
1507 cred_1.authorize(creds_http_1)
1508
1509 creds_http_2 = HttpMockSequence([])
1510 cred_2.authorize(creds_http_2)
1511
1512 self.request1.http = creds_http_1
1513 self.request2.http = creds_http_2
1514
1515 batch.add(self.request1, callback=callbacks.f)
1516 batch.add(self.request2, callback=callbacks.f)
1517 batch.execute(http=http)
1518
1519 self.assertEqual(None, callbacks.responses["1"])
1520 self.assertEqual(401, callbacks.exceptions["1"].resp.status)
1521 self.assertEqual(
1522 "Authorization Required", callbacks.exceptions["1"].resp.reason
1523 )
1524 self.assertEqual({u"baz": u"qux"}, callbacks.responses["2"])
1525 self.assertEqual(None, callbacks.exceptions["2"])
1526
1527 def test_execute_global_callback(self):
1528 callbacks = Callbacks()
1529 batch = BatchHttpRequest(callback=callbacks.f)
1530
1531 batch.add(self.request1)
1532 batch.add(self.request2)
1533 http = HttpMockSequence(
1534 [
1535 (
1536 {
1537 "status": "200",
1538 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1539 },
1540 BATCH_RESPONSE,
1541 )
1542 ]
1543 )
1544 batch.execute(http=http)
1545 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1546 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1547
1548 def test_execute_batch_http_error(self):
1549 callbacks = Callbacks()
1550 batch = BatchHttpRequest(callback=callbacks.f)
1551
1552 batch.add(self.request1)
1553 batch.add(self.request2)
1554 http = HttpMockSequence(
1555 [
1556 (
1557 {
1558 "status": "200",
1559 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1560 },
1561 BATCH_ERROR_RESPONSE,
1562 )
1563 ]
1564 )
1565 batch.execute(http=http)
1566 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1567 expected = (
1568 "<HttpError 403 when requesting "
1569 "https://www.googleapis.com/someapi/v1/collection/?foo=bar returned "
Muad Mohameda341c5a2020-11-08 20:30:02 +00001570 '"Access Not Configured". '
1571 'Details: "Access Not Configured">'
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001572 )
1573 self.assertEqual(expected, str(callbacks.exceptions["2"]))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001574
Joe Gregorio5c120db2012-08-23 09:13:55 -04001575
Joe Gregorioba5c7902012-08-03 12:48:16 -04001576class TestRequestUriTooLong(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001577 def test_turn_get_into_post(self):
1578 def _postproc(resp, content):
1579 return content
Joe Gregorioba5c7902012-08-03 12:48:16 -04001580
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001581 http = HttpMockSequence(
1582 [
1583 ({"status": "200"}, "echo_request_body"),
1584 ({"status": "200"}, "echo_request_headers"),
1585 ]
1586 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001587
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001588 # Send a long query parameter.
1589 query = {"q": "a" * MAX_URI_LENGTH + "?&"}
1590 req = HttpRequest(
1591 http,
1592 _postproc,
1593 "http://example.com?" + urlencode(query),
1594 method="GET",
1595 body=None,
1596 headers={},
1597 methodId="foo",
1598 resumable=None,
1599 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001600
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001601 # Query parameters should be sent in the body.
1602 response = req.execute()
1603 self.assertEqual(b"q=" + b"a" * MAX_URI_LENGTH + b"%3F%26", response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001604
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001605 # Extra headers should be set.
1606 response = req.execute()
1607 self.assertEqual("GET", response["x-http-method-override"])
1608 self.assertEqual(str(MAX_URI_LENGTH + 8), response["content-length"])
1609 self.assertEqual("application/x-www-form-urlencoded", response["content-type"])
Joe Gregorioba5c7902012-08-03 12:48:16 -04001610
Joe Gregorio5c120db2012-08-23 09:13:55 -04001611
1612class TestStreamSlice(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001613 """Test _StreamSlice."""
Joe Gregorio5c120db2012-08-23 09:13:55 -04001614
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001615 def setUp(self):
1616 self.stream = BytesIO(b"0123456789")
Joe Gregorio5c120db2012-08-23 09:13:55 -04001617
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001618 def test_read(self):
1619 s = _StreamSlice(self.stream, 0, 4)
1620 self.assertEqual(b"", s.read(0))
1621 self.assertEqual(b"0", s.read(1))
1622 self.assertEqual(b"123", s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001623
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001624 def test_read_too_much(self):
1625 s = _StreamSlice(self.stream, 1, 4)
1626 self.assertEqual(b"1234", s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001627
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001628 def test_read_all(self):
1629 s = _StreamSlice(self.stream, 2, 1)
1630 self.assertEqual(b"2", s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001631
Ali Afshar164f37e2013-01-07 14:05:45 -08001632
1633class TestResponseCallback(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001634 """Test adding callbacks to responses."""
Ali Afshar164f37e2013-01-07 14:05:45 -08001635
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001636 def test_ensure_response_callback(self):
1637 m = JsonModel()
1638 request = HttpRequest(
1639 None,
1640 m.response,
1641 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1642 method="POST",
1643 body="{}",
1644 headers={"content-type": "application/json"},
1645 )
1646 h = HttpMockSequence([({"status": 200}, "{}")])
1647 responses = []
1648
1649 def _on_response(resp, responses=responses):
1650 responses.append(resp)
1651
1652 request.add_response_callback(_on_response)
1653 request.execute(http=h)
1654 self.assertEqual(1, len(responses))
Ali Afshar164f37e2013-01-07 14:05:45 -08001655
1656
Craig Gurnik8e55b762015-01-20 15:00:10 -05001657class TestHttpMock(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001658 def test_default_response_headers(self):
1659 http = HttpMock(datafile("zoo.json"))
1660 resp, content = http.request("http://example.com")
1661 self.assertEqual(resp.status, 200)
Craig Gurnik8e55b762015-01-20 15:00:10 -05001662
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001663 def test_error_response(self):
1664 http = HttpMock(datafile("bad_request.json"), {"status": "400"})
1665 model = JsonModel()
1666 request = HttpRequest(
1667 http,
1668 model.response,
1669 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1670 method="GET",
1671 headers={},
1672 )
1673 self.assertRaises(HttpError, request.execute)
Alan Briolat26b01002015-08-14 00:13:57 +01001674
Craig Gurnik8e55b762015-01-20 15:00:10 -05001675
Igor Maravić22435292017-01-19 22:28:22 +01001676class TestHttpBuild(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001677 original_socket_default_timeout = None
Igor Maravić22435292017-01-19 22:28:22 +01001678
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001679 @classmethod
1680 def setUpClass(cls):
1681 cls.original_socket_default_timeout = socket.getdefaulttimeout()
Igor Maravić22435292017-01-19 22:28:22 +01001682
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001683 @classmethod
1684 def tearDownClass(cls):
1685 socket.setdefaulttimeout(cls.original_socket_default_timeout)
Igor Maravić22435292017-01-19 22:28:22 +01001686
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001687 def test_build_http_sets_default_timeout_if_none_specified(self):
1688 socket.setdefaulttimeout(None)
1689 http = build_http()
1690 self.assertIsInstance(http.timeout, int)
1691 self.assertGreater(http.timeout, 0)
Igor Maravić22435292017-01-19 22:28:22 +01001692
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001693 def test_build_http_default_timeout_can_be_overridden(self):
1694 socket.setdefaulttimeout(1.5)
1695 http = build_http()
1696 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
Igor Maravić22435292017-01-19 22:28:22 +01001697
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001698 def test_build_http_default_timeout_can_be_set_to_zero(self):
1699 socket.setdefaulttimeout(0)
1700 http = build_http()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001701 self.assertEqual(http.timeout, 0)
Bu Sun Kim790e7022020-09-11 20:18:06 -06001702
Bu Sun Kimb3b773f2020-03-11 12:58:16 -07001703 def test_build_http_default_308_is_excluded_as_redirect(self):
1704 http = build_http()
1705 self.assertTrue(308 not in http.redirect_codes)
Igor Maravić22435292017-01-19 22:28:22 +01001706
1707
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001708if __name__ == "__main__":
1709 logging.getLogger().setLevel(logging.ERROR)
1710 unittest.main()