blob: 88b9d5925f1a240e9e6f0842078a0d1cc0dbef2a [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
Joe Gregorio708388c2012-06-15 13:43:04 -0400446class TestMediaIoBaseDownload(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700447 def setUp(self):
448 http = HttpMock(datafile("zoo.json"), {"status": "200"})
449 zoo = build("zoo", "v1", http=http)
450 self.request = zoo.animals().get_media(name="Lion")
451 self.fd = BytesIO()
Joe Gregorio708388c2012-06-15 13:43:04 -0400452
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700453 def test_media_io_base_download(self):
454 self.request.http = HttpMockSequence(
455 [
456 ({"status": "200", "content-range": "0-2/5"}, b"123"),
457 ({"status": "200", "content-range": "3-4/5"}, b"45"),
458 ]
459 )
460 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400461
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700462 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400463
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700464 self.assertEqual(self.fd, download._fd)
465 self.assertEqual(3, download._chunksize)
466 self.assertEqual(0, download._progress)
467 self.assertEqual(None, download._total_size)
468 self.assertEqual(False, download._done)
469 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400470
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700471 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400472
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700473 self.assertEqual(self.fd.getvalue(), b"123")
474 self.assertEqual(False, done)
475 self.assertEqual(3, download._progress)
476 self.assertEqual(5, download._total_size)
477 self.assertEqual(3, status.resumable_progress)
Joe Gregorio708388c2012-06-15 13:43:04 -0400478
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700479 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400480
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700481 self.assertEqual(self.fd.getvalue(), b"12345")
482 self.assertEqual(True, done)
483 self.assertEqual(5, download._progress)
484 self.assertEqual(5, download._total_size)
Joe Gregorio708388c2012-06-15 13:43:04 -0400485
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700486 def test_media_io_base_download_custom_request_headers(self):
487 self.request.http = HttpMockSequence(
488 [
489 (
490 {"status": "200", "content-range": "0-2/5"},
491 "echo_request_headers_as_json",
492 ),
493 (
494 {"status": "200", "content-range": "3-4/5"},
495 "echo_request_headers_as_json",
496 ),
497 ]
498 )
499 self.assertEqual(True, self.request.http.follow_redirects)
Joe Gregorio708388c2012-06-15 13:43:04 -0400500
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700501 self.request.headers["Cache-Control"] = "no-store"
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400502
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700503 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400504
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700505 self.assertEqual(download._headers.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400506
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700507 status, done = download.next_chunk()
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400508
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700509 result = json.loads(self.fd.getvalue().decode("utf-8"))
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400510
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700511 # assert that that the header we added to the original request is
512 # sent up to the server on each call to next_chunk
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400513
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700514 self.assertEqual(result.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400515
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700516 download._fd = self.fd = BytesIO()
517 status, done = download.next_chunk()
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400518
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700519 result = json.loads(self.fd.getvalue().decode("utf-8"))
520 self.assertEqual(result.get("Cache-Control"), "no-store")
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400521
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700522 def test_media_io_base_download_handle_redirects(self):
523 self.request.http = HttpMockSequence(
524 [
525 (
526 {
527 "status": "200",
528 "content-location": "https://secure.example.net/lion",
529 },
530 b"",
531 ),
532 ({"status": "200", "content-range": "0-2/5"}, b"abc"),
533 ]
534 )
Chris McDonough0dc81bf2018-07-19 11:19:58 -0400535
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700536 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400537
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700538 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400539
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700540 self.assertEqual("https://secure.example.net/lion", download._uri)
Joe Gregorio708388c2012-06-15 13:43:04 -0400541
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700542 def test_media_io_base_download_handle_4xx(self):
543 self.request.http = HttpMockSequence([({"status": "400"}, "")])
Joe Gregorio708388c2012-06-15 13:43:04 -0400544
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700545 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio708388c2012-06-15 13:43:04 -0400546
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700547 try:
548 status, done = download.next_chunk()
549 self.fail("Should raise an exception")
550 except HttpError:
551 pass
Joe Gregorio708388c2012-06-15 13:43:04 -0400552
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700553 # Even after raising an exception we can pick up where we left off.
554 self.request.http = HttpMockSequence(
555 [({"status": "200", "content-range": "0-2/5"}, b"123")]
556 )
Joe Gregorio708388c2012-06-15 13:43:04 -0400557
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700558 status, done = download.next_chunk()
Joe Gregorio708388c2012-06-15 13:43:04 -0400559
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700560 self.assertEqual(self.fd.getvalue(), b"123")
Joe Gregorio708388c2012-06-15 13:43:04 -0400561
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700562 def test_media_io_base_download_retries_connection_errors(self):
563 self.request.http = HttpMockWithErrors(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100564 5, {"status": "200", "content-range": "0-2/3"}, b"123"
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700565 )
Joe Gregorio708388c2012-06-15 13:43:04 -0400566
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700567 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
568 download._sleep = lambda _x: 0 # do nothing
569 download._rand = lambda: 10
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100570
Damian Gadomskic7516a22020-03-23 20:39:21 +0100571 status, done = download.next_chunk(num_retries=5)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100572
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700573 self.assertEqual(self.fd.getvalue(), b"123")
574 self.assertEqual(True, done)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100575
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700576 def test_media_io_base_download_retries_5xx(self):
577 self.request.http = HttpMockSequence(
578 [
579 ({"status": "500"}, ""),
580 ({"status": "500"}, ""),
581 ({"status": "500"}, ""),
582 ({"status": "200", "content-range": "0-2/5"}, b"123"),
583 ({"status": "503"}, ""),
584 ({"status": "503"}, ""),
585 ({"status": "503"}, ""),
586 ({"status": "200", "content-range": "3-4/5"}, b"45"),
587 ]
588 )
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100589
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700590 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400591
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700592 self.assertEqual(self.fd, download._fd)
593 self.assertEqual(3, download._chunksize)
594 self.assertEqual(0, download._progress)
595 self.assertEqual(None, download._total_size)
596 self.assertEqual(False, download._done)
597 self.assertEqual(self.request.uri, download._uri)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400598
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700599 # Set time.sleep and random.random stubs.
600 sleeptimes = []
601 download._sleep = lambda x: sleeptimes.append(x)
602 download._rand = lambda: 10
Joe Gregorio9086bd32013-06-14 16:32:05 -0400603
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700604 status, done = download.next_chunk(num_retries=3)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400605
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700606 # Check for exponential backoff using the rand function above.
607 self.assertEqual([20, 40, 80], sleeptimes)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400608
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700609 self.assertEqual(self.fd.getvalue(), b"123")
610 self.assertEqual(False, done)
611 self.assertEqual(3, download._progress)
612 self.assertEqual(5, download._total_size)
613 self.assertEqual(3, status.resumable_progress)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400614
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700615 # Reset time.sleep stub.
616 del sleeptimes[0 : len(sleeptimes)]
Joe Gregorio9086bd32013-06-14 16:32:05 -0400617
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700618 status, done = download.next_chunk(num_retries=3)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400619
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700620 # Check for exponential backoff using the rand function above.
621 self.assertEqual([20, 40, 80], sleeptimes)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400622
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700623 self.assertEqual(self.fd.getvalue(), b"12345")
624 self.assertEqual(True, done)
625 self.assertEqual(5, download._progress)
626 self.assertEqual(5, download._total_size)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400627
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700628 def test_media_io_base_download_empty_file(self):
629 self.request.http = HttpMockSequence(
630 [({"status": "200", "content-range": "0-0/0"}, b"")]
631 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400632
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700633 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
andrewnestera4a44cf2017-03-31 16:09:31 +0300634
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700635 self.assertEqual(self.fd, download._fd)
636 self.assertEqual(0, download._progress)
637 self.assertEqual(None, download._total_size)
638 self.assertEqual(False, download._done)
639 self.assertEqual(self.request.uri, download._uri)
andrewnestera4a44cf2017-03-31 16:09:31 +0300640
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700641 status, done = download.next_chunk()
andrewnestera4a44cf2017-03-31 16:09:31 +0300642
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700643 self.assertEqual(True, done)
644 self.assertEqual(0, download._progress)
645 self.assertEqual(0, download._total_size)
646 self.assertEqual(0, status.progress())
andrewnestera4a44cf2017-03-31 16:09:31 +0300647
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700648 def test_media_io_base_download_unknown_media_size(self):
649 self.request.http = HttpMockSequence([({"status": "200"}, b"123")])
andrewnestera4a44cf2017-03-31 16:09:31 +0300650
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700651 download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
Daniel44067782018-01-16 23:17:56 +0100652
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700653 self.assertEqual(self.fd, download._fd)
654 self.assertEqual(0, download._progress)
655 self.assertEqual(None, download._total_size)
656 self.assertEqual(False, download._done)
657 self.assertEqual(self.request.uri, download._uri)
Daniel44067782018-01-16 23:17:56 +0100658
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700659 status, done = download.next_chunk()
Daniel44067782018-01-16 23:17:56 +0100660
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700661 self.assertEqual(self.fd.getvalue(), b"123")
662 self.assertEqual(True, done)
663 self.assertEqual(3, download._progress)
664 self.assertEqual(None, download._total_size)
665 self.assertEqual(0, status.progress())
Daniel44067782018-01-16 23:17:56 +0100666
667
Joe Gregorio66f57522011-11-30 11:00:00 -0500668EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
669Content-Type: application/json
670MIME-Version: 1.0
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500671Host: www.googleapis.com
672content-length: 2\r\n\r\n{}"""
673
674
675NO_BODY_EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
676Content-Type: application/json
677MIME-Version: 1.0
678Host: www.googleapis.com
679content-length: 0\r\n\r\n"""
Joe Gregorio66f57522011-11-30 11:00:00 -0500680
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -0400681NO_BODY_EXPECTED_GET = """GET /someapi/v1/collection/?foo=bar HTTP/1.1
682Content-Type: application/json
683MIME-Version: 1.0
684Host: www.googleapis.com\r\n\r\n"""
685
Joe Gregorio66f57522011-11-30 11:00:00 -0500686
687RESPONSE = """HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400688Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500689Content-Length: 14
690ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
691
692
INADA Naoki09157612015-03-25 01:51:03 +0900693BATCH_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio66f57522011-11-30 11:00:00 -0500694Content-Type: application/http
695Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400696Content-ID: <randomness + 1>
Joe Gregorio66f57522011-11-30 11:00:00 -0500697
698HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400699Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500700Content-Length: 14
701ETag: "etag/pony"\r\n\r\n{"foo": 42}
702
703--batch_foobarbaz
704Content-Type: application/http
705Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400706Content-ID: <randomness + 2>
Joe Gregorio66f57522011-11-30 11:00:00 -0500707
708HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400709Content-Type: application/json
Joe Gregorio66f57522011-11-30 11:00:00 -0500710Content-Length: 14
711ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
712--batch_foobarbaz--"""
713
Joe Gregorio5d1171b2012-01-05 10:48:24 -0500714
INADA Naoki09157612015-03-25 01:51:03 +0900715BATCH_ERROR_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio3fb93672012-07-25 11:31:11 -0400716Content-Type: application/http
717Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400718Content-ID: <randomness + 1>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400719
720HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400721Content-Type: application/json
Joe Gregorio3fb93672012-07-25 11:31:11 -0400722Content-Length: 14
723ETag: "etag/pony"\r\n\r\n{"foo": 42}
724
725--batch_foobarbaz
726Content-Type: application/http
727Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400728Content-ID: <randomness + 2>
Joe Gregorio3fb93672012-07-25 11:31:11 -0400729
730HTTP/1.1 403 Access Not Configured
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400731Content-Type: application/json
732Content-Length: 245
733ETag: "etag/sheep"\r\n\r\n{
Joe Gregorio3fb93672012-07-25 11:31:11 -0400734 "error": {
735 "errors": [
736 {
737 "domain": "usageLimits",
738 "reason": "accessNotConfigured",
739 "message": "Access Not Configured",
740 "debugInfo": "QuotaState: BLOCKED"
741 }
742 ],
743 "code": 403,
744 "message": "Access Not Configured"
745 }
746}
747
748--batch_foobarbaz--"""
749
750
INADA Naoki09157612015-03-25 01:51:03 +0900751BATCH_RESPONSE_WITH_401 = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500752Content-Type: application/http
753Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400754Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500755
Joe Gregorioc752e332012-07-11 14:43:52 -0400756HTTP/1.1 401 Authorization Required
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400757Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500758Content-Length: 14
759ETag: "etag/pony"\r\n\r\n{"error": {"message":
760 "Authorizaton failed."}}
761
762--batch_foobarbaz
763Content-Type: application/http
764Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400765Content-ID: <randomness + 2>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500766
767HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400768Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500769Content-Length: 14
770ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
771--batch_foobarbaz--"""
772
773
INADA Naoki09157612015-03-25 01:51:03 +0900774BATCH_SINGLE_RESPONSE = b"""--batch_foobarbaz
Joe Gregorio654f4a22012-02-09 14:15:44 -0500775Content-Type: application/http
776Content-Transfer-Encoding: binary
Chris McDonough3cf5e602018-07-18 16:18:38 -0400777Content-ID: <randomness + 1>
Joe Gregorio654f4a22012-02-09 14:15:44 -0500778
779HTTP/1.1 200 OK
Joe Gregorio20b54fb2012-07-26 09:59:35 -0400780Content-Type: application/json
Joe Gregorio654f4a22012-02-09 14:15:44 -0500781Content-Length: 14
782ETag: "etag/pony"\r\n\r\n{"foo": 42}
783--batch_foobarbaz--"""
784
eesheeshc6425a02016-02-12 15:07:06 +0000785
786USER_RATE_LIMIT_EXCEEDED_RESPONSE = """{
787 "error": {
788 "errors": [
789 {
790 "domain": "usageLimits",
791 "reason": "userRateLimitExceeded",
792 "message": "User Rate Limit Exceeded"
793 }
794 ],
795 "code": 403,
796 "message": "User Rate Limit Exceeded"
797 }
798}"""
799
800
801RATE_LIMIT_EXCEEDED_RESPONSE = """{
802 "error": {
803 "errors": [
804 {
805 "domain": "usageLimits",
806 "reason": "rateLimitExceeded",
807 "message": "Rate Limit Exceeded"
808 }
809 ],
810 "code": 403,
811 "message": "Rate Limit Exceeded"
812 }
813}"""
814
815
816NOT_CONFIGURED_RESPONSE = """{
817 "error": {
818 "errors": [
819 {
820 "domain": "usageLimits",
821 "reason": "accessNotConfigured",
822 "message": "Access Not Configured"
823 }
824 ],
825 "code": 403,
826 "message": "Access Not Configured"
827 }
828}"""
829
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -0800830LIST_NOT_CONFIGURED_RESPONSE = """[
831 "error": {
832 "errors": [
833 {
834 "domain": "usageLimits",
835 "reason": "accessNotConfigured",
836 "message": "Access Not Configured"
837 }
838 ],
839 "code": 403,
840 "message": "Access Not Configured"
841 }
842]"""
843
Joe Gregorio654f4a22012-02-09 14:15:44 -0500844
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700845class Callbacks(object):
846 def __init__(self):
847 self.responses = {}
848 self.exceptions = {}
849
850 def f(self, request_id, response, exception):
851 self.responses[request_id] = response
852 self.exceptions[request_id] = exception
Joe Gregorio654f4a22012-02-09 14:15:44 -0500853
854
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500855class TestHttpRequest(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700856 def test_unicode(self):
857 http = HttpMock(datafile("zoo.json"), headers={"status": "200"})
858 model = JsonModel()
859 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
860 method = u"POST"
861 request = HttpRequest(
862 http,
863 model.response,
864 uri,
865 method=method,
866 body=u"{}",
867 headers={"content-type": "application/json"},
868 )
869 request.execute()
870 self.assertEqual(uri, http.uri)
871 self.assertEqual(str, type(http.uri))
872 self.assertEqual(method, http.method)
873 self.assertEqual(str, type(http.method))
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500874
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700875 def test_empty_content_type(self):
876 """Test for #284"""
877 http = HttpMock(None, headers={"status": 200})
878 uri = u"https://www.googleapis.com/someapi/v1/upload/?foo=bar"
879 method = u"POST"
880 request = HttpRequest(
881 http, _postproc_none, uri, method=method, headers={"content-type": ""}
882 )
883 request.execute()
884 self.assertEqual("", http.headers.get("content-type"))
Xiaofei Wang20b67582019-07-17 11:16:53 -0700885
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700886 def test_no_retry_connection_errors(self):
887 model = JsonModel()
888 request = HttpRequest(
889 HttpMockWithNonRetriableErrors(1, {"status": "200"}, '{"foo": "bar"}'),
890 model.response,
891 u"https://www.example.com/json_api_endpoint",
892 )
893 request._sleep = lambda _x: 0 # do nothing
894 request._rand = lambda: 10
895 with self.assertRaises(socket.error):
896 response = request.execute(num_retries=3)
eesheeshc6425a02016-02-12 15:07:06 +0000897
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700898 def test_retry_connection_errors_non_resumable(self):
899 model = JsonModel()
900 request = HttpRequest(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100901 HttpMockWithErrors(5, {"status": "200"}, '{"foo": "bar"}'),
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700902 model.response,
903 u"https://www.example.com/json_api_endpoint",
904 )
905 request._sleep = lambda _x: 0 # do nothing
906 request._rand = lambda: 10
Damian Gadomskic7516a22020-03-23 20:39:21 +0100907 response = request.execute(num_retries=5)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700908 self.assertEqual({u"foo": u"bar"}, response)
eesheeshc6425a02016-02-12 15:07:06 +0000909
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700910 def test_retry_connection_errors_resumable(self):
911 with open(datafile("small.png"), "rb") as small_png_file:
912 small_png_fd = BytesIO(small_png_file.read())
913 upload = MediaIoBaseUpload(
914 fd=small_png_fd, mimetype="image/png", chunksize=500, resumable=True
915 )
916 model = JsonModel()
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100917
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700918 request = HttpRequest(
919 HttpMockWithErrors(
Damian Gadomskic7516a22020-03-23 20:39:21 +0100920 5, {"status": "200", "location": "location"}, '{"foo": "bar"}'
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700921 ),
922 model.response,
923 u"https://www.example.com/file_upload",
924 method="POST",
925 resumable=upload,
926 )
927 request._sleep = lambda _x: 0 # do nothing
928 request._rand = lambda: 10
Damian Gadomskic7516a22020-03-23 20:39:21 +0100929 response = request.execute(num_retries=5)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700930 self.assertEqual({u"foo": u"bar"}, response)
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100931
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700932 def test_retry(self):
933 num_retries = 5
934 resp_seq = [({"status": "500"}, "")] * (num_retries - 3)
935 resp_seq.append(({"status": "403"}, RATE_LIMIT_EXCEEDED_RESPONSE))
936 resp_seq.append(({"status": "403"}, USER_RATE_LIMIT_EXCEEDED_RESPONSE))
937 resp_seq.append(({"status": "429"}, ""))
938 resp_seq.append(({"status": "200"}, "{}"))
Sergiy Byelozyorov703c92c2015-12-21 23:27:48 +0100939
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700940 http = HttpMockSequence(resp_seq)
941 model = JsonModel()
942 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
943 method = u"POST"
944 request = HttpRequest(
945 http,
946 model.response,
947 uri,
948 method=method,
949 body=u"{}",
950 headers={"content-type": "application/json"},
951 )
Joe Gregorio9086bd32013-06-14 16:32:05 -0400952
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700953 sleeptimes = []
954 request._sleep = lambda x: sleeptimes.append(x)
955 request._rand = lambda: 10
Joe Gregorio9086bd32013-06-14 16:32:05 -0400956
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700957 request.execute(num_retries=num_retries)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400958
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700959 self.assertEqual(num_retries, len(sleeptimes))
960 for retry_num in range(num_retries):
961 self.assertEqual(10 * 2 ** (retry_num + 1), sleeptimes[retry_num])
Joe Gregorio9086bd32013-06-14 16:32:05 -0400962
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700963 def test_no_retry_succeeds(self):
964 num_retries = 5
965 resp_seq = [({"status": "200"}, "{}")] * (num_retries)
Joe Gregorio9086bd32013-06-14 16:32:05 -0400966
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 )
eesheeshc6425a02016-02-12 15:07:06 +0000979
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700980 sleeptimes = []
981 request._sleep = lambda x: sleeptimes.append(x)
982 request._rand = lambda: 10
eesheeshc6425a02016-02-12 15:07:06 +0000983
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700984 request.execute(num_retries=num_retries)
eesheeshc6425a02016-02-12 15:07:06 +0000985
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700986 self.assertEqual(0, len(sleeptimes))
eesheeshc6425a02016-02-12 15:07:06 +0000987
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700988 def test_no_retry_fails_fast(self):
989 http = HttpMockSequence([({"status": "500"}, ""), ({"status": "200"}, "{}")])
990 model = JsonModel()
991 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
992 method = u"POST"
993 request = HttpRequest(
994 http,
995 model.response,
996 uri,
997 method=method,
998 body=u"{}",
999 headers={"content-type": "application/json"},
1000 )
eesheeshc6425a02016-02-12 15:07:06 +00001001
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001002 request._rand = lambda: 1.0
1003 request._sleep = mock.MagicMock()
Joe Gregorio9086bd32013-06-14 16:32:05 -04001004
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001005 with self.assertRaises(HttpError):
1006 request.execute()
1007 request._sleep.assert_not_called()
Joe Gregorio9086bd32013-06-14 16:32:05 -04001008
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001009 def test_no_retry_403_not_configured_fails_fast(self):
1010 http = HttpMockSequence(
1011 [({"status": "403"}, NOT_CONFIGURED_RESPONSE), ({"status": "200"}, "{}")]
1012 )
1013 model = JsonModel()
1014 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1015 method = u"POST"
1016 request = HttpRequest(
1017 http,
1018 model.response,
1019 uri,
1020 method=method,
1021 body=u"{}",
1022 headers={"content-type": "application/json"},
1023 )
Joe Gregorio9086bd32013-06-14 16:32:05 -04001024
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001025 request._rand = lambda: 1.0
1026 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001027
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001028 with self.assertRaises(HttpError):
1029 request.execute()
1030 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001031
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001032 def test_no_retry_403_fails_fast(self):
1033 http = HttpMockSequence([({"status": "403"}, ""), ({"status": "200"}, "{}")])
1034 model = JsonModel()
1035 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1036 method = u"POST"
1037 request = HttpRequest(
1038 http,
1039 model.response,
1040 uri,
1041 method=method,
1042 body=u"{}",
1043 headers={"content-type": "application/json"},
1044 )
eesheeshc6425a02016-02-12 15:07:06 +00001045
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001046 request._rand = lambda: 1.0
1047 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001048
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001049 with self.assertRaises(HttpError):
1050 request.execute()
1051 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001052
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001053 def test_no_retry_401_fails_fast(self):
1054 http = HttpMockSequence([({"status": "401"}, ""), ({"status": "200"}, "{}")])
1055 model = JsonModel()
1056 uri = u"https://www.googleapis.com/someapi/v1/collection/?foo=bar"
1057 method = u"POST"
1058 request = HttpRequest(
1059 http,
1060 model.response,
1061 uri,
1062 method=method,
1063 body=u"{}",
1064 headers={"content-type": "application/json"},
1065 )
eesheeshc6425a02016-02-12 15:07:06 +00001066
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001067 request._rand = lambda: 1.0
1068 request._sleep = mock.MagicMock()
eesheeshc6425a02016-02-12 15:07:06 +00001069
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001070 with self.assertRaises(HttpError):
1071 request.execute()
1072 request._sleep.assert_not_called()
eesheeshc6425a02016-02-12 15:07:06 +00001073
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001074 def test_no_retry_403_list_fails(self):
1075 http = HttpMockSequence(
1076 [
1077 ({"status": "403"}, LIST_NOT_CONFIGURED_RESPONSE),
1078 ({"status": "200"}, "{}"),
1079 ]
1080 )
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 )
Joe Gregorio83f2ee62012-12-06 15:25:54 -05001092
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001093 request._rand = lambda: 1.0
1094 request._sleep = mock.MagicMock()
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001095
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001096 with self.assertRaises(HttpError):
1097 request.execute()
1098 request._sleep.assert_not_called()
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001099
Dmitry Frenkelf3348f92020-07-15 13:05:58 -07001100 def test_null_postproc(self):
1101 resp, content = HttpRequest.null_postproc("foo", "bar")
1102 self.assertEqual(resp, "foo")
1103 self.assertEqual(content, "bar")
1104
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001105
Joe Gregorio66f57522011-11-30 11:00:00 -05001106class TestBatch(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001107 def setUp(self):
1108 model = JsonModel()
1109 self.request1 = HttpRequest(
1110 None,
1111 model.response,
1112 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1113 method="POST",
1114 body="{}",
1115 headers={"content-type": "application/json"},
1116 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001117
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001118 self.request2 = HttpRequest(
1119 None,
1120 model.response,
1121 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1122 method="GET",
1123 body="",
1124 headers={"content-type": "application/json"},
1125 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001126
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001127 def test_id_to_from_content_id_header(self):
1128 batch = BatchHttpRequest()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001129 self.assertEqual("12", batch._header_to_id(batch._id_to_header("12")))
Joe Gregorio66f57522011-11-30 11:00:00 -05001130
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001131 def test_invalid_content_id_header(self):
1132 batch = BatchHttpRequest()
1133 self.assertRaises(BatchError, batch._header_to_id, "[foo+x]")
1134 self.assertRaises(BatchError, batch._header_to_id, "foo+1")
1135 self.assertRaises(BatchError, batch._header_to_id, "<foo>")
Joe Gregorio66f57522011-11-30 11:00:00 -05001136
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001137 def test_serialize_request(self):
1138 batch = BatchHttpRequest()
1139 request = HttpRequest(
1140 None,
1141 None,
1142 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1143 method="POST",
1144 body=u"{}",
1145 headers={"content-type": "application/json"},
1146 methodId=None,
1147 resumable=None,
1148 )
1149 s = batch._serialize_request(request).splitlines()
1150 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001151
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001152 def test_serialize_request_media_body(self):
1153 batch = BatchHttpRequest()
1154 f = open(datafile("small.png"), "rb")
1155 body = f.read()
1156 f.close()
Joe Gregorio66f57522011-11-30 11:00:00 -05001157
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001158 request = HttpRequest(
1159 None,
1160 None,
1161 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1162 method="POST",
1163 body=body,
1164 headers={"content-type": "application/json"},
1165 methodId=None,
1166 resumable=None,
1167 )
1168 # Just testing it shouldn't raise an exception.
1169 s = batch._serialize_request(request).splitlines()
Joe Gregorio66f57522011-11-30 11:00:00 -05001170
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001171 def test_serialize_request_no_body(self):
1172 batch = BatchHttpRequest()
1173 request = HttpRequest(
1174 None,
1175 None,
1176 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1177 method="POST",
1178 body=b"",
1179 headers={"content-type": "application/json"},
1180 methodId=None,
1181 resumable=None,
1182 )
1183 s = batch._serialize_request(request).splitlines()
1184 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001185
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001186 def test_serialize_get_request_no_body(self):
1187 batch = BatchHttpRequest()
1188 request = HttpRequest(
1189 None,
1190 None,
1191 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1192 method="GET",
1193 body=None,
1194 headers={"content-type": "application/json"},
1195 methodId=None,
1196 resumable=None,
1197 )
1198 s = batch._serialize_request(request).splitlines()
1199 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001200
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001201 def test_deserialize_response(self):
1202 batch = BatchHttpRequest()
1203 resp, content = batch._deserialize_response(RESPONSE)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001204
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001205 self.assertEqual(200, resp.status)
1206 self.assertEqual("OK", resp.reason)
1207 self.assertEqual(11, resp.version)
1208 self.assertEqual('{"answer": 42}', content)
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001209
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001210 def test_new_id(self):
1211 batch = BatchHttpRequest()
Joe Gregorio66f57522011-11-30 11:00:00 -05001212
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001213 id_ = batch._new_id()
1214 self.assertEqual("1", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001215
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001216 id_ = batch._new_id()
1217 self.assertEqual("2", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001218
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001219 batch.add(self.request1, request_id="3")
Joe Gregorio66f57522011-11-30 11:00:00 -05001220
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001221 id_ = batch._new_id()
1222 self.assertEqual("4", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001223
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001224 def test_add(self):
1225 batch = BatchHttpRequest()
1226 batch.add(self.request1, request_id="1")
1227 self.assertRaises(KeyError, batch.add, self.request1, request_id="1")
Joe Gregorio66f57522011-11-30 11:00:00 -05001228
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001229 def test_add_fail_for_over_limit(self):
1230 from googleapiclient.http import MAX_BATCH_LIMIT
Joe Gregorio66f57522011-11-30 11:00:00 -05001231
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001232 batch = BatchHttpRequest()
1233 for i in range(0, MAX_BATCH_LIMIT):
1234 batch.add(
1235 HttpRequest(
1236 None,
1237 None,
1238 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1239 method="POST",
1240 body="{}",
1241 headers={"content-type": "application/json"},
1242 )
Chris McDonough3cf5e602018-07-18 16:18:38 -04001243 )
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001244 self.assertRaises(BatchError, batch.add, self.request1)
Chris McDonough3cf5e602018-07-18 16:18:38 -04001245
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001246 def test_add_fail_for_resumable(self):
1247 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001248
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001249 upload = MediaFileUpload(datafile("small.png"), chunksize=500, resumable=True)
1250 self.request1.resumable = upload
1251 with self.assertRaises(BatchError) as batch_error:
1252 batch.add(self.request1, request_id="1")
1253 str(batch_error.exception)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001254
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001255 def test_execute_empty_batch_no_http(self):
1256 batch = BatchHttpRequest()
1257 ret = batch.execute()
1258 self.assertEqual(None, ret)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001259
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001260 def test_execute(self):
1261 batch = BatchHttpRequest()
1262 callbacks = Callbacks()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001263
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001264 batch.add(self.request1, callback=callbacks.f)
1265 batch.add(self.request2, callback=callbacks.f)
1266 http = HttpMockSequence(
1267 [
1268 (
1269 {
1270 "status": "200",
1271 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1272 },
1273 BATCH_RESPONSE,
1274 )
1275 ]
1276 )
1277 batch.execute(http=http)
1278 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1279 self.assertEqual(None, callbacks.exceptions["1"])
1280 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1281 self.assertEqual(None, callbacks.exceptions["2"])
Gabriel Garcia23174be2016-05-25 17:28:07 +02001282
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001283 def test_execute_request_body(self):
1284 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001285
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001286 batch.add(self.request1)
1287 batch.add(self.request2)
1288 http = HttpMockSequence(
1289 [
1290 (
1291 {
1292 "status": "200",
1293 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1294 },
1295 "echo_request_body",
1296 )
1297 ]
1298 )
1299 try:
1300 batch.execute(http=http)
1301 self.fail("Should raise exception")
1302 except BatchError as e:
1303 boundary, _ = e.content.split(None, 1)
1304 self.assertEqual("--", boundary[:2])
1305 parts = e.content.split(boundary)
1306 self.assertEqual(4, len(parts))
1307 self.assertEqual("", parts[0])
1308 self.assertEqual("--", parts[3].rstrip())
1309 header = parts[1].splitlines()[1]
1310 self.assertEqual("Content-Type: application/http", header)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001311
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001312 def test_execute_request_body_with_custom_long_request_ids(self):
1313 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001314
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001315 batch.add(self.request1, request_id="abc" * 20)
1316 batch.add(self.request2, request_id="def" * 20)
1317 http = HttpMockSequence(
1318 [
1319 (
1320 {
1321 "status": "200",
1322 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1323 },
1324 "echo_request_body",
1325 )
1326 ]
1327 )
1328 try:
1329 batch.execute(http=http)
1330 self.fail("Should raise exception")
1331 except BatchError as e:
1332 boundary, _ = e.content.split(None, 1)
1333 self.assertEqual("--", boundary[:2])
1334 parts = e.content.split(boundary)
1335 self.assertEqual(4, len(parts))
1336 self.assertEqual("", parts[0])
1337 self.assertEqual("--", parts[3].rstrip())
1338 for partindex, request_id in ((1, "abc" * 20), (2, "def" * 20)):
1339 lines = parts[partindex].splitlines()
1340 for n, line in enumerate(lines):
1341 if line.startswith("Content-ID:"):
1342 # assert correct header folding
1343 self.assertTrue(line.endswith("+"), line)
1344 header_continuation = lines[n + 1]
1345 self.assertEqual(
1346 header_continuation,
1347 " %s>" % request_id,
1348 header_continuation,
1349 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001350
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001351 def test_execute_initial_refresh_oauth2(self):
1352 batch = BatchHttpRequest()
1353 callbacks = Callbacks()
1354 cred = MockCredentials("Foo", expired=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001355
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001356 http = HttpMockSequence(
1357 [
1358 (
1359 {
1360 "status": "200",
1361 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1362 },
1363 BATCH_SINGLE_RESPONSE,
1364 )
1365 ]
1366 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001367
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001368 cred.authorize(http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001369
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001370 batch.add(self.request1, callback=callbacks.f)
1371 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001372
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001373 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1374 self.assertIsNone(callbacks.exceptions["1"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001375
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001376 self.assertEqual(1, cred._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001377
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001378 self.assertEqual(1, cred._authorized)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001379
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001380 self.assertEqual(1, cred._applied)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001381
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001382 def test_execute_refresh_and_retry_on_401(self):
1383 batch = BatchHttpRequest()
1384 callbacks = Callbacks()
1385 cred_1 = MockCredentials("Foo")
1386 cred_2 = MockCredentials("Bar")
Joe Gregorio654f4a22012-02-09 14:15:44 -05001387
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001388 http = HttpMockSequence(
1389 [
1390 (
1391 {
1392 "status": "200",
1393 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1394 },
1395 BATCH_RESPONSE_WITH_401,
1396 ),
1397 (
1398 {
1399 "status": "200",
1400 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1401 },
1402 BATCH_SINGLE_RESPONSE,
1403 ),
1404 ]
1405 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001406
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001407 creds_http_1 = HttpMockSequence([])
1408 cred_1.authorize(creds_http_1)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001409
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001410 creds_http_2 = HttpMockSequence([])
1411 cred_2.authorize(creds_http_2)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001412
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001413 self.request1.http = creds_http_1
1414 self.request2.http = creds_http_2
Joe Gregorio654f4a22012-02-09 14:15:44 -05001415
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001416 batch.add(self.request1, callback=callbacks.f)
1417 batch.add(self.request2, 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.assertEqual(None, callbacks.exceptions["1"])
1422 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1423 self.assertEqual(None, callbacks.exceptions["2"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001424
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001425 self.assertEqual(1, cred_1._refreshed)
1426 self.assertEqual(0, cred_2._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001427
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001428 self.assertEqual(1, cred_1._authorized)
1429 self.assertEqual(1, cred_2._authorized)
Joe Gregorio66f57522011-11-30 11:00:00 -05001430
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001431 self.assertEqual(1, cred_2._applied)
1432 self.assertEqual(2, cred_1._applied)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001433
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001434 def test_http_errors_passed_to_callback(self):
1435 batch = BatchHttpRequest()
1436 callbacks = Callbacks()
1437 cred_1 = MockCredentials("Foo")
1438 cred_2 = MockCredentials("Bar")
Joe Gregorio3fb93672012-07-25 11:31:11 -04001439
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001440 http = HttpMockSequence(
1441 [
1442 (
1443 {
1444 "status": "200",
1445 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1446 },
1447 BATCH_RESPONSE_WITH_401,
1448 ),
1449 (
1450 {
1451 "status": "200",
1452 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1453 },
1454 BATCH_RESPONSE_WITH_401,
1455 ),
1456 ]
1457 )
1458
1459 creds_http_1 = HttpMockSequence([])
1460 cred_1.authorize(creds_http_1)
1461
1462 creds_http_2 = HttpMockSequence([])
1463 cred_2.authorize(creds_http_2)
1464
1465 self.request1.http = creds_http_1
1466 self.request2.http = creds_http_2
1467
1468 batch.add(self.request1, callback=callbacks.f)
1469 batch.add(self.request2, callback=callbacks.f)
1470 batch.execute(http=http)
1471
1472 self.assertEqual(None, callbacks.responses["1"])
1473 self.assertEqual(401, callbacks.exceptions["1"].resp.status)
1474 self.assertEqual(
1475 "Authorization Required", callbacks.exceptions["1"].resp.reason
1476 )
1477 self.assertEqual({u"baz": u"qux"}, callbacks.responses["2"])
1478 self.assertEqual(None, callbacks.exceptions["2"])
1479
1480 def test_execute_global_callback(self):
1481 callbacks = Callbacks()
1482 batch = BatchHttpRequest(callback=callbacks.f)
1483
1484 batch.add(self.request1)
1485 batch.add(self.request2)
1486 http = HttpMockSequence(
1487 [
1488 (
1489 {
1490 "status": "200",
1491 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1492 },
1493 BATCH_RESPONSE,
1494 )
1495 ]
1496 )
1497 batch.execute(http=http)
1498 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1499 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1500
1501 def test_execute_batch_http_error(self):
1502 callbacks = Callbacks()
1503 batch = BatchHttpRequest(callback=callbacks.f)
1504
1505 batch.add(self.request1)
1506 batch.add(self.request2)
1507 http = HttpMockSequence(
1508 [
1509 (
1510 {
1511 "status": "200",
1512 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1513 },
1514 BATCH_ERROR_RESPONSE,
1515 )
1516 ]
1517 )
1518 batch.execute(http=http)
1519 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1520 expected = (
1521 "<HttpError 403 when requesting "
1522 "https://www.googleapis.com/someapi/v1/collection/?foo=bar returned "
1523 '"Access Not Configured">'
1524 )
1525 self.assertEqual(expected, str(callbacks.exceptions["2"]))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001526
Joe Gregorio5c120db2012-08-23 09:13:55 -04001527
Joe Gregorioba5c7902012-08-03 12:48:16 -04001528class TestRequestUriTooLong(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001529 def test_turn_get_into_post(self):
1530 def _postproc(resp, content):
1531 return content
Joe Gregorioba5c7902012-08-03 12:48:16 -04001532
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001533 http = HttpMockSequence(
1534 [
1535 ({"status": "200"}, "echo_request_body"),
1536 ({"status": "200"}, "echo_request_headers"),
1537 ]
1538 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001539
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001540 # Send a long query parameter.
1541 query = {"q": "a" * MAX_URI_LENGTH + "?&"}
1542 req = HttpRequest(
1543 http,
1544 _postproc,
1545 "http://example.com?" + urlencode(query),
1546 method="GET",
1547 body=None,
1548 headers={},
1549 methodId="foo",
1550 resumable=None,
1551 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001552
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001553 # Query parameters should be sent in the body.
1554 response = req.execute()
1555 self.assertEqual(b"q=" + b"a" * MAX_URI_LENGTH + b"%3F%26", response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001556
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001557 # Extra headers should be set.
1558 response = req.execute()
1559 self.assertEqual("GET", response["x-http-method-override"])
1560 self.assertEqual(str(MAX_URI_LENGTH + 8), response["content-length"])
1561 self.assertEqual("application/x-www-form-urlencoded", response["content-type"])
Joe Gregorioba5c7902012-08-03 12:48:16 -04001562
Joe Gregorio5c120db2012-08-23 09:13:55 -04001563
1564class TestStreamSlice(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001565 """Test _StreamSlice."""
Joe Gregorio5c120db2012-08-23 09:13:55 -04001566
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001567 def setUp(self):
1568 self.stream = BytesIO(b"0123456789")
Joe Gregorio5c120db2012-08-23 09:13:55 -04001569
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001570 def test_read(self):
1571 s = _StreamSlice(self.stream, 0, 4)
1572 self.assertEqual(b"", s.read(0))
1573 self.assertEqual(b"0", s.read(1))
1574 self.assertEqual(b"123", s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001575
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001576 def test_read_too_much(self):
1577 s = _StreamSlice(self.stream, 1, 4)
1578 self.assertEqual(b"1234", s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001579
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001580 def test_read_all(self):
1581 s = _StreamSlice(self.stream, 2, 1)
1582 self.assertEqual(b"2", s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001583
Ali Afshar164f37e2013-01-07 14:05:45 -08001584
1585class TestResponseCallback(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001586 """Test adding callbacks to responses."""
Ali Afshar164f37e2013-01-07 14:05:45 -08001587
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001588 def test_ensure_response_callback(self):
1589 m = JsonModel()
1590 request = HttpRequest(
1591 None,
1592 m.response,
1593 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1594 method="POST",
1595 body="{}",
1596 headers={"content-type": "application/json"},
1597 )
1598 h = HttpMockSequence([({"status": 200}, "{}")])
1599 responses = []
1600
1601 def _on_response(resp, responses=responses):
1602 responses.append(resp)
1603
1604 request.add_response_callback(_on_response)
1605 request.execute(http=h)
1606 self.assertEqual(1, len(responses))
Ali Afshar164f37e2013-01-07 14:05:45 -08001607
1608
Craig Gurnik8e55b762015-01-20 15:00:10 -05001609class TestHttpMock(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001610 def test_default_response_headers(self):
1611 http = HttpMock(datafile("zoo.json"))
1612 resp, content = http.request("http://example.com")
1613 self.assertEqual(resp.status, 200)
Craig Gurnik8e55b762015-01-20 15:00:10 -05001614
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001615 def test_error_response(self):
1616 http = HttpMock(datafile("bad_request.json"), {"status": "400"})
1617 model = JsonModel()
1618 request = HttpRequest(
1619 http,
1620 model.response,
1621 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1622 method="GET",
1623 headers={},
1624 )
1625 self.assertRaises(HttpError, request.execute)
Alan Briolat26b01002015-08-14 00:13:57 +01001626
Craig Gurnik8e55b762015-01-20 15:00:10 -05001627
Igor Maravić22435292017-01-19 22:28:22 +01001628class TestHttpBuild(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001629 original_socket_default_timeout = None
Igor Maravić22435292017-01-19 22:28:22 +01001630
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001631 @classmethod
1632 def setUpClass(cls):
1633 cls.original_socket_default_timeout = socket.getdefaulttimeout()
Igor Maravić22435292017-01-19 22:28:22 +01001634
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001635 @classmethod
1636 def tearDownClass(cls):
1637 socket.setdefaulttimeout(cls.original_socket_default_timeout)
Igor Maravić22435292017-01-19 22:28:22 +01001638
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001639 def test_build_http_sets_default_timeout_if_none_specified(self):
1640 socket.setdefaulttimeout(None)
1641 http = build_http()
1642 self.assertIsInstance(http.timeout, int)
1643 self.assertGreater(http.timeout, 0)
Igor Maravić22435292017-01-19 22:28:22 +01001644
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001645 def test_build_http_default_timeout_can_be_overridden(self):
1646 socket.setdefaulttimeout(1.5)
1647 http = build_http()
1648 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
Igor Maravić22435292017-01-19 22:28:22 +01001649
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001650 def test_build_http_default_timeout_can_be_set_to_zero(self):
1651 socket.setdefaulttimeout(0)
1652 http = build_http()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001653 self.assertEqual(http.timeout, 0)
Bu Sun Kimb3b773f2020-03-11 12:58:16 -07001654
1655 def test_build_http_default_308_is_excluded_as_redirect(self):
1656 http = build_http()
1657 self.assertTrue(308 not in http.redirect_codes)
Igor Maravić22435292017-01-19 22:28:22 +01001658
1659
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001660if __name__ == "__main__":
1661 logging.getLogger().setLevel(logging.ERROR)
1662 unittest.main()