blob: 2c0756e6c535fbbea733296bd836c67257fafab6 [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
Nilayan Bhattacharya90ffb852017-12-05 15:30:32 -08001100
Joe Gregorio66f57522011-11-30 11:00:00 -05001101class TestBatch(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001102 def setUp(self):
1103 model = JsonModel()
1104 self.request1 = HttpRequest(
1105 None,
1106 model.response,
1107 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1108 method="POST",
1109 body="{}",
1110 headers={"content-type": "application/json"},
1111 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001112
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001113 self.request2 = HttpRequest(
1114 None,
1115 model.response,
1116 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1117 method="GET",
1118 body="",
1119 headers={"content-type": "application/json"},
1120 )
Joe Gregorio66f57522011-11-30 11:00:00 -05001121
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001122 def test_id_to_from_content_id_header(self):
1123 batch = BatchHttpRequest()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001124 self.assertEqual("12", batch._header_to_id(batch._id_to_header("12")))
Joe Gregorio66f57522011-11-30 11:00:00 -05001125
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001126 def test_invalid_content_id_header(self):
1127 batch = BatchHttpRequest()
1128 self.assertRaises(BatchError, batch._header_to_id, "[foo+x]")
1129 self.assertRaises(BatchError, batch._header_to_id, "foo+1")
1130 self.assertRaises(BatchError, batch._header_to_id, "<foo>")
Joe Gregorio66f57522011-11-30 11:00:00 -05001131
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001132 def test_serialize_request(self):
1133 batch = BatchHttpRequest()
1134 request = HttpRequest(
1135 None,
1136 None,
1137 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1138 method="POST",
1139 body=u"{}",
1140 headers={"content-type": "application/json"},
1141 methodId=None,
1142 resumable=None,
1143 )
1144 s = batch._serialize_request(request).splitlines()
1145 self.assertEqual(EXPECTED.splitlines(), s)
Joe Gregorio66f57522011-11-30 11:00:00 -05001146
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001147 def test_serialize_request_media_body(self):
1148 batch = BatchHttpRequest()
1149 f = open(datafile("small.png"), "rb")
1150 body = f.read()
1151 f.close()
Joe Gregorio66f57522011-11-30 11:00:00 -05001152
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001153 request = HttpRequest(
1154 None,
1155 None,
1156 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1157 method="POST",
1158 body=body,
1159 headers={"content-type": "application/json"},
1160 methodId=None,
1161 resumable=None,
1162 )
1163 # Just testing it shouldn't raise an exception.
1164 s = batch._serialize_request(request).splitlines()
Joe Gregorio66f57522011-11-30 11:00:00 -05001165
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001166 def test_serialize_request_no_body(self):
1167 batch = BatchHttpRequest()
1168 request = HttpRequest(
1169 None,
1170 None,
1171 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1172 method="POST",
1173 body=b"",
1174 headers={"content-type": "application/json"},
1175 methodId=None,
1176 resumable=None,
1177 )
1178 s = batch._serialize_request(request).splitlines()
1179 self.assertEqual(NO_BODY_EXPECTED.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001180
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001181 def test_serialize_get_request_no_body(self):
1182 batch = BatchHttpRequest()
1183 request = HttpRequest(
1184 None,
1185 None,
1186 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1187 method="GET",
1188 body=None,
1189 headers={"content-type": "application/json"},
1190 methodId=None,
1191 resumable=None,
1192 )
1193 s = batch._serialize_request(request).splitlines()
1194 self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
Joe Gregoriodd813822012-01-25 10:32:47 -05001195
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001196 def test_deserialize_response(self):
1197 batch = BatchHttpRequest()
1198 resp, content = batch._deserialize_response(RESPONSE)
Joe Gregorio5d1171b2012-01-05 10:48:24 -05001199
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001200 self.assertEqual(200, resp.status)
1201 self.assertEqual("OK", resp.reason)
1202 self.assertEqual(11, resp.version)
1203 self.assertEqual('{"answer": 42}', content)
Pepper Lebeck-Jobe30ecbd42015-06-12 11:45:57 -04001204
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001205 def test_new_id(self):
1206 batch = BatchHttpRequest()
Joe Gregorio66f57522011-11-30 11:00:00 -05001207
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001208 id_ = batch._new_id()
1209 self.assertEqual("1", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001210
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001211 id_ = batch._new_id()
1212 self.assertEqual("2", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001213
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001214 batch.add(self.request1, request_id="3")
Joe Gregorio66f57522011-11-30 11:00:00 -05001215
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001216 id_ = batch._new_id()
1217 self.assertEqual("4", id_)
Joe Gregorio66f57522011-11-30 11:00:00 -05001218
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001219 def test_add(self):
1220 batch = BatchHttpRequest()
1221 batch.add(self.request1, request_id="1")
1222 self.assertRaises(KeyError, batch.add, self.request1, request_id="1")
Joe Gregorio66f57522011-11-30 11:00:00 -05001223
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001224 def test_add_fail_for_over_limit(self):
1225 from googleapiclient.http import MAX_BATCH_LIMIT
Joe Gregorio66f57522011-11-30 11:00:00 -05001226
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001227 batch = BatchHttpRequest()
1228 for i in range(0, MAX_BATCH_LIMIT):
1229 batch.add(
1230 HttpRequest(
1231 None,
1232 None,
1233 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1234 method="POST",
1235 body="{}",
1236 headers={"content-type": "application/json"},
1237 )
Chris McDonough3cf5e602018-07-18 16:18:38 -04001238 )
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001239 self.assertRaises(BatchError, batch.add, self.request1)
Chris McDonough3cf5e602018-07-18 16:18:38 -04001240
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001241 def test_add_fail_for_resumable(self):
1242 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001243
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001244 upload = MediaFileUpload(datafile("small.png"), chunksize=500, resumable=True)
1245 self.request1.resumable = upload
1246 with self.assertRaises(BatchError) as batch_error:
1247 batch.add(self.request1, request_id="1")
1248 str(batch_error.exception)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001249
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001250 def test_execute_empty_batch_no_http(self):
1251 batch = BatchHttpRequest()
1252 ret = batch.execute()
1253 self.assertEqual(None, ret)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001254
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001255 def test_execute(self):
1256 batch = BatchHttpRequest()
1257 callbacks = Callbacks()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001258
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001259 batch.add(self.request1, callback=callbacks.f)
1260 batch.add(self.request2, callback=callbacks.f)
1261 http = HttpMockSequence(
1262 [
1263 (
1264 {
1265 "status": "200",
1266 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1267 },
1268 BATCH_RESPONSE,
1269 )
1270 ]
1271 )
1272 batch.execute(http=http)
1273 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1274 self.assertEqual(None, callbacks.exceptions["1"])
1275 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1276 self.assertEqual(None, callbacks.exceptions["2"])
Gabriel Garcia23174be2016-05-25 17:28:07 +02001277
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001278 def test_execute_request_body(self):
1279 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001280
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001281 batch.add(self.request1)
1282 batch.add(self.request2)
1283 http = HttpMockSequence(
1284 [
1285 (
1286 {
1287 "status": "200",
1288 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1289 },
1290 "echo_request_body",
1291 )
1292 ]
1293 )
1294 try:
1295 batch.execute(http=http)
1296 self.fail("Should raise exception")
1297 except BatchError as e:
1298 boundary, _ = e.content.split(None, 1)
1299 self.assertEqual("--", boundary[:2])
1300 parts = e.content.split(boundary)
1301 self.assertEqual(4, len(parts))
1302 self.assertEqual("", parts[0])
1303 self.assertEqual("--", parts[3].rstrip())
1304 header = parts[1].splitlines()[1]
1305 self.assertEqual("Content-Type: application/http", header)
Gabriel Garcia23174be2016-05-25 17:28:07 +02001306
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001307 def test_execute_request_body_with_custom_long_request_ids(self):
1308 batch = BatchHttpRequest()
Gabriel Garcia23174be2016-05-25 17:28:07 +02001309
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001310 batch.add(self.request1, request_id="abc" * 20)
1311 batch.add(self.request2, request_id="def" * 20)
1312 http = HttpMockSequence(
1313 [
1314 (
1315 {
1316 "status": "200",
1317 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1318 },
1319 "echo_request_body",
1320 )
1321 ]
1322 )
1323 try:
1324 batch.execute(http=http)
1325 self.fail("Should raise exception")
1326 except BatchError as e:
1327 boundary, _ = e.content.split(None, 1)
1328 self.assertEqual("--", boundary[:2])
1329 parts = e.content.split(boundary)
1330 self.assertEqual(4, len(parts))
1331 self.assertEqual("", parts[0])
1332 self.assertEqual("--", parts[3].rstrip())
1333 for partindex, request_id in ((1, "abc" * 20), (2, "def" * 20)):
1334 lines = parts[partindex].splitlines()
1335 for n, line in enumerate(lines):
1336 if line.startswith("Content-ID:"):
1337 # assert correct header folding
1338 self.assertTrue(line.endswith("+"), line)
1339 header_continuation = lines[n + 1]
1340 self.assertEqual(
1341 header_continuation,
1342 " %s>" % request_id,
1343 header_continuation,
1344 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001345
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001346 def test_execute_initial_refresh_oauth2(self):
1347 batch = BatchHttpRequest()
1348 callbacks = Callbacks()
1349 cred = MockCredentials("Foo", expired=True)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001350
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001351 http = HttpMockSequence(
1352 [
1353 (
1354 {
1355 "status": "200",
1356 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1357 },
1358 BATCH_SINGLE_RESPONSE,
1359 )
1360 ]
1361 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001362
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001363 cred.authorize(http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001364
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001365 batch.add(self.request1, callback=callbacks.f)
1366 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001367
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001368 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1369 self.assertIsNone(callbacks.exceptions["1"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001370
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001371 self.assertEqual(1, cred._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001372
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001373 self.assertEqual(1, cred._authorized)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001374
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001375 self.assertEqual(1, cred._applied)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001376
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001377 def test_execute_refresh_and_retry_on_401(self):
1378 batch = BatchHttpRequest()
1379 callbacks = Callbacks()
1380 cred_1 = MockCredentials("Foo")
1381 cred_2 = MockCredentials("Bar")
Joe Gregorio654f4a22012-02-09 14:15:44 -05001382
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001383 http = HttpMockSequence(
1384 [
1385 (
1386 {
1387 "status": "200",
1388 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1389 },
1390 BATCH_RESPONSE_WITH_401,
1391 ),
1392 (
1393 {
1394 "status": "200",
1395 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1396 },
1397 BATCH_SINGLE_RESPONSE,
1398 ),
1399 ]
1400 )
Joe Gregorio654f4a22012-02-09 14:15:44 -05001401
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001402 creds_http_1 = HttpMockSequence([])
1403 cred_1.authorize(creds_http_1)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001404
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001405 creds_http_2 = HttpMockSequence([])
1406 cred_2.authorize(creds_http_2)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001407
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001408 self.request1.http = creds_http_1
1409 self.request2.http = creds_http_2
Joe Gregorio654f4a22012-02-09 14:15:44 -05001410
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001411 batch.add(self.request1, callback=callbacks.f)
1412 batch.add(self.request2, callback=callbacks.f)
1413 batch.execute(http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001414
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001415 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1416 self.assertEqual(None, callbacks.exceptions["1"])
1417 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1418 self.assertEqual(None, callbacks.exceptions["2"])
Joe Gregorio654f4a22012-02-09 14:15:44 -05001419
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001420 self.assertEqual(1, cred_1._refreshed)
1421 self.assertEqual(0, cred_2._refreshed)
Joe Gregorio654f4a22012-02-09 14:15:44 -05001422
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001423 self.assertEqual(1, cred_1._authorized)
1424 self.assertEqual(1, cred_2._authorized)
Joe Gregorio66f57522011-11-30 11:00:00 -05001425
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001426 self.assertEqual(1, cred_2._applied)
1427 self.assertEqual(2, cred_1._applied)
Joe Gregoriod0bd3882011-11-22 09:49:47 -05001428
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001429 def test_http_errors_passed_to_callback(self):
1430 batch = BatchHttpRequest()
1431 callbacks = Callbacks()
1432 cred_1 = MockCredentials("Foo")
1433 cred_2 = MockCredentials("Bar")
Joe Gregorio3fb93672012-07-25 11:31:11 -04001434
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_RESPONSE_WITH_401,
1450 ),
1451 ]
1452 )
1453
1454 creds_http_1 = HttpMockSequence([])
1455 cred_1.authorize(creds_http_1)
1456
1457 creds_http_2 = HttpMockSequence([])
1458 cred_2.authorize(creds_http_2)
1459
1460 self.request1.http = creds_http_1
1461 self.request2.http = creds_http_2
1462
1463 batch.add(self.request1, callback=callbacks.f)
1464 batch.add(self.request2, callback=callbacks.f)
1465 batch.execute(http=http)
1466
1467 self.assertEqual(None, callbacks.responses["1"])
1468 self.assertEqual(401, callbacks.exceptions["1"].resp.status)
1469 self.assertEqual(
1470 "Authorization Required", callbacks.exceptions["1"].resp.reason
1471 )
1472 self.assertEqual({u"baz": u"qux"}, callbacks.responses["2"])
1473 self.assertEqual(None, callbacks.exceptions["2"])
1474
1475 def test_execute_global_callback(self):
1476 callbacks = Callbacks()
1477 batch = BatchHttpRequest(callback=callbacks.f)
1478
1479 batch.add(self.request1)
1480 batch.add(self.request2)
1481 http = HttpMockSequence(
1482 [
1483 (
1484 {
1485 "status": "200",
1486 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1487 },
1488 BATCH_RESPONSE,
1489 )
1490 ]
1491 )
1492 batch.execute(http=http)
1493 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1494 self.assertEqual({"baz": "qux"}, callbacks.responses["2"])
1495
1496 def test_execute_batch_http_error(self):
1497 callbacks = Callbacks()
1498 batch = BatchHttpRequest(callback=callbacks.f)
1499
1500 batch.add(self.request1)
1501 batch.add(self.request2)
1502 http = HttpMockSequence(
1503 [
1504 (
1505 {
1506 "status": "200",
1507 "content-type": 'multipart/mixed; boundary="batch_foobarbaz"',
1508 },
1509 BATCH_ERROR_RESPONSE,
1510 )
1511 ]
1512 )
1513 batch.execute(http=http)
1514 self.assertEqual({"foo": 42}, callbacks.responses["1"])
1515 expected = (
1516 "<HttpError 403 when requesting "
1517 "https://www.googleapis.com/someapi/v1/collection/?foo=bar returned "
1518 '"Access Not Configured">'
1519 )
1520 self.assertEqual(expected, str(callbacks.exceptions["2"]))
Ali Afshar6f11ea12012-02-07 10:32:14 -05001521
Joe Gregorio5c120db2012-08-23 09:13:55 -04001522
Joe Gregorioba5c7902012-08-03 12:48:16 -04001523class TestRequestUriTooLong(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001524 def test_turn_get_into_post(self):
1525 def _postproc(resp, content):
1526 return content
Joe Gregorioba5c7902012-08-03 12:48:16 -04001527
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001528 http = HttpMockSequence(
1529 [
1530 ({"status": "200"}, "echo_request_body"),
1531 ({"status": "200"}, "echo_request_headers"),
1532 ]
1533 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001534
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001535 # Send a long query parameter.
1536 query = {"q": "a" * MAX_URI_LENGTH + "?&"}
1537 req = HttpRequest(
1538 http,
1539 _postproc,
1540 "http://example.com?" + urlencode(query),
1541 method="GET",
1542 body=None,
1543 headers={},
1544 methodId="foo",
1545 resumable=None,
1546 )
Joe Gregorioba5c7902012-08-03 12:48:16 -04001547
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001548 # Query parameters should be sent in the body.
1549 response = req.execute()
1550 self.assertEqual(b"q=" + b"a" * MAX_URI_LENGTH + b"%3F%26", response)
Joe Gregorioba5c7902012-08-03 12:48:16 -04001551
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001552 # Extra headers should be set.
1553 response = req.execute()
1554 self.assertEqual("GET", response["x-http-method-override"])
1555 self.assertEqual(str(MAX_URI_LENGTH + 8), response["content-length"])
1556 self.assertEqual("application/x-www-form-urlencoded", response["content-type"])
Joe Gregorioba5c7902012-08-03 12:48:16 -04001557
Joe Gregorio5c120db2012-08-23 09:13:55 -04001558
1559class TestStreamSlice(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001560 """Test _StreamSlice."""
Joe Gregorio5c120db2012-08-23 09:13:55 -04001561
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001562 def setUp(self):
1563 self.stream = BytesIO(b"0123456789")
Joe Gregorio5c120db2012-08-23 09:13:55 -04001564
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001565 def test_read(self):
1566 s = _StreamSlice(self.stream, 0, 4)
1567 self.assertEqual(b"", s.read(0))
1568 self.assertEqual(b"0", s.read(1))
1569 self.assertEqual(b"123", s.read())
Joe Gregorio5c120db2012-08-23 09:13:55 -04001570
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001571 def test_read_too_much(self):
1572 s = _StreamSlice(self.stream, 1, 4)
1573 self.assertEqual(b"1234", s.read(6))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001574
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001575 def test_read_all(self):
1576 s = _StreamSlice(self.stream, 2, 1)
1577 self.assertEqual(b"2", s.read(-1))
Joe Gregorio5c120db2012-08-23 09:13:55 -04001578
Ali Afshar164f37e2013-01-07 14:05:45 -08001579
1580class TestResponseCallback(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001581 """Test adding callbacks to responses."""
Ali Afshar164f37e2013-01-07 14:05:45 -08001582
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001583 def test_ensure_response_callback(self):
1584 m = JsonModel()
1585 request = HttpRequest(
1586 None,
1587 m.response,
1588 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1589 method="POST",
1590 body="{}",
1591 headers={"content-type": "application/json"},
1592 )
1593 h = HttpMockSequence([({"status": 200}, "{}")])
1594 responses = []
1595
1596 def _on_response(resp, responses=responses):
1597 responses.append(resp)
1598
1599 request.add_response_callback(_on_response)
1600 request.execute(http=h)
1601 self.assertEqual(1, len(responses))
Ali Afshar164f37e2013-01-07 14:05:45 -08001602
1603
Craig Gurnik8e55b762015-01-20 15:00:10 -05001604class TestHttpMock(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001605 def test_default_response_headers(self):
1606 http = HttpMock(datafile("zoo.json"))
1607 resp, content = http.request("http://example.com")
1608 self.assertEqual(resp.status, 200)
Craig Gurnik8e55b762015-01-20 15:00:10 -05001609
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001610 def test_error_response(self):
1611 http = HttpMock(datafile("bad_request.json"), {"status": "400"})
1612 model = JsonModel()
1613 request = HttpRequest(
1614 http,
1615 model.response,
1616 "https://www.googleapis.com/someapi/v1/collection/?foo=bar",
1617 method="GET",
1618 headers={},
1619 )
1620 self.assertRaises(HttpError, request.execute)
Alan Briolat26b01002015-08-14 00:13:57 +01001621
Craig Gurnik8e55b762015-01-20 15:00:10 -05001622
Igor Maravić22435292017-01-19 22:28:22 +01001623class TestHttpBuild(unittest.TestCase):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001624 original_socket_default_timeout = None
Igor Maravić22435292017-01-19 22:28:22 +01001625
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001626 @classmethod
1627 def setUpClass(cls):
1628 cls.original_socket_default_timeout = socket.getdefaulttimeout()
Igor Maravić22435292017-01-19 22:28:22 +01001629
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001630 @classmethod
1631 def tearDownClass(cls):
1632 socket.setdefaulttimeout(cls.original_socket_default_timeout)
Igor Maravić22435292017-01-19 22:28:22 +01001633
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001634 def test_build_http_sets_default_timeout_if_none_specified(self):
1635 socket.setdefaulttimeout(None)
1636 http = build_http()
1637 self.assertIsInstance(http.timeout, int)
1638 self.assertGreater(http.timeout, 0)
Igor Maravić22435292017-01-19 22:28:22 +01001639
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001640 def test_build_http_default_timeout_can_be_overridden(self):
1641 socket.setdefaulttimeout(1.5)
1642 http = build_http()
1643 self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
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_set_to_zero(self):
1646 socket.setdefaulttimeout(0)
1647 http = build_http()
Bu Sun Kim1cf3cbc2020-03-12 12:38:23 -07001648 self.assertEqual(http.timeout, 0)
Bu Sun Kimb3b773f2020-03-11 12:58:16 -07001649
1650 def test_build_http_default_308_is_excluded_as_redirect(self):
1651 http = build_http()
1652 self.assertTrue(308 not in http.redirect_codes)
Igor Maravić22435292017-01-19 22:28:22 +01001653
1654
Bu Sun Kim66bb32c2019-10-30 10:11:58 -07001655if __name__ == "__main__":
1656 logging.getLogger().setLevel(logging.ERROR)
1657 unittest.main()