1# Copyright 2017 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import http.client
16import io
17
18import mock
19import pytest
20
21from google.resumable_media import _download
22from google.resumable_media import common
23
24
25EXAMPLE_URL = (
26    "https://www.googleapis.com/download/storage/v1/b/{BUCKET}/o/{OBJECT}?alt=media"
27)
28
29
30class TestDownloadBase(object):
31    def test_constructor_defaults(self):
32        download = _download.DownloadBase(EXAMPLE_URL)
33        assert download.media_url == EXAMPLE_URL
34        assert download._stream is None
35        assert download.start is None
36        assert download.end is None
37        assert download._headers == {}
38        assert not download._finished
39        _check_retry_strategy(download)
40
41    def test_constructor_explicit(self):
42        start = 11
43        end = 10001
44        headers = {"foof": "barf"}
45        download = _download.DownloadBase(
46            EXAMPLE_URL,
47            stream=mock.sentinel.stream,
48            start=start,
49            end=end,
50            headers=headers,
51        )
52        assert download.media_url == EXAMPLE_URL
53        assert download._stream is mock.sentinel.stream
54        assert download.start == start
55        assert download.end == end
56        assert download._headers is headers
57        assert not download._finished
58        _check_retry_strategy(download)
59
60    def test_finished_property(self):
61        download = _download.DownloadBase(EXAMPLE_URL)
62        # Default value of @property.
63        assert not download.finished
64
65        # Make sure we cannot set it on public @property.
66        with pytest.raises(AttributeError):
67            download.finished = False
68
69        # Set it privately and then check the @property.
70        download._finished = True
71        assert download.finished
72
73    def test__get_status_code(self):
74        with pytest.raises(NotImplementedError) as exc_info:
75            _download.DownloadBase._get_status_code(None)
76
77        exc_info.match("virtual")
78
79    def test__get_headers(self):
80        with pytest.raises(NotImplementedError) as exc_info:
81            _download.DownloadBase._get_headers(None)
82
83        exc_info.match("virtual")
84
85    def test__get_body(self):
86        with pytest.raises(NotImplementedError) as exc_info:
87            _download.DownloadBase._get_body(None)
88
89        exc_info.match("virtual")
90
91
92class TestDownload(object):
93    def test__prepare_request_already_finished(self):
94        download = _download.Download(EXAMPLE_URL)
95        download._finished = True
96        with pytest.raises(ValueError):
97            download._prepare_request()
98
99    def test__prepare_request(self):
100        download1 = _download.Download(EXAMPLE_URL)
101        method1, url1, payload1, headers1 = download1._prepare_request()
102        assert method1 == "GET"
103        assert url1 == EXAMPLE_URL
104        assert payload1 is None
105        assert headers1 == {}
106
107        download2 = _download.Download(EXAMPLE_URL, start=53)
108        method2, url2, payload2, headers2 = download2._prepare_request()
109        assert method2 == "GET"
110        assert url2 == EXAMPLE_URL
111        assert payload2 is None
112        assert headers2 == {"range": "bytes=53-"}
113
114    def test__prepare_request_with_headers(self):
115        headers = {"spoonge": "borb"}
116        download = _download.Download(EXAMPLE_URL, start=11, end=111, headers=headers)
117        method, url, payload, new_headers = download._prepare_request()
118        assert method == "GET"
119        assert url == EXAMPLE_URL
120        assert payload is None
121        assert new_headers is headers
122        assert headers == {"range": "bytes=11-111", "spoonge": "borb"}
123
124    def test__process_response(self):
125        download = _download.Download(EXAMPLE_URL)
126        _fix_up_virtual(download)
127
128        # Make sure **not finished** before.
129        assert not download.finished
130        response = mock.Mock(status_code=int(http.client.OK), spec=["status_code"])
131        ret_val = download._process_response(response)
132        assert ret_val is None
133        # Make sure **finished** after.
134        assert download.finished
135
136    def test__process_response_bad_status(self):
137        download = _download.Download(EXAMPLE_URL)
138        _fix_up_virtual(download)
139
140        # Make sure **not finished** before.
141        assert not download.finished
142        response = mock.Mock(
143            status_code=int(http.client.NOT_FOUND), spec=["status_code"]
144        )
145        with pytest.raises(common.InvalidResponse) as exc_info:
146            download._process_response(response)
147
148        error = exc_info.value
149        assert error.response is response
150        assert len(error.args) == 5
151        assert error.args[1] == response.status_code
152        assert error.args[3] == http.client.OK
153        assert error.args[4] == http.client.PARTIAL_CONTENT
154        # Make sure **finished** even after a failure.
155        assert download.finished
156
157    def test_consume(self):
158        download = _download.Download(EXAMPLE_URL)
159        with pytest.raises(NotImplementedError) as exc_info:
160            download.consume(None)
161
162        exc_info.match("virtual")
163
164
165class TestChunkedDownload(object):
166    def test_constructor_defaults(self):
167        chunk_size = 256
168        stream = mock.sentinel.stream
169        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream)
170        assert download.media_url == EXAMPLE_URL
171        assert download.chunk_size == chunk_size
172        assert download.start == 0
173        assert download.end is None
174        assert download._headers == {}
175        assert not download._finished
176        _check_retry_strategy(download)
177        assert download._stream is stream
178        assert download._bytes_downloaded == 0
179        assert download._total_bytes is None
180        assert not download._invalid
181
182    def test_constructor_bad_start(self):
183        with pytest.raises(ValueError):
184            _download.ChunkedDownload(EXAMPLE_URL, 256, None, start=-11)
185
186    def test_bytes_downloaded_property(self):
187        download = _download.ChunkedDownload(EXAMPLE_URL, 256, None)
188        # Default value of @property.
189        assert download.bytes_downloaded == 0
190
191        # Make sure we cannot set it on public @property.
192        with pytest.raises(AttributeError):
193            download.bytes_downloaded = 1024
194
195        # Set it privately and then check the @property.
196        download._bytes_downloaded = 128
197        assert download.bytes_downloaded == 128
198
199    def test_total_bytes_property(self):
200        download = _download.ChunkedDownload(EXAMPLE_URL, 256, None)
201        # Default value of @property.
202        assert download.total_bytes is None
203
204        # Make sure we cannot set it on public @property.
205        with pytest.raises(AttributeError):
206            download.total_bytes = 65536
207
208        # Set it privately and then check the @property.
209        download._total_bytes = 8192
210        assert download.total_bytes == 8192
211
212    def test__get_byte_range(self):
213        chunk_size = 512
214        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, None)
215        curr_start, curr_end = download._get_byte_range()
216        assert curr_start == 0
217        assert curr_end == chunk_size - 1
218
219    def test__get_byte_range_with_end(self):
220        chunk_size = 512
221        start = 1024
222        end = 1151
223        download = _download.ChunkedDownload(
224            EXAMPLE_URL, chunk_size, None, start=start, end=end
225        )
226        curr_start, curr_end = download._get_byte_range()
227        assert curr_start == start
228        assert curr_end == end
229        # Make sure this is less than the chunk size.
230        actual_size = curr_end - curr_start + 1
231        assert actual_size < chunk_size
232
233    def test__get_byte_range_with_total_bytes(self):
234        chunk_size = 512
235        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, None)
236        total_bytes = 207
237        download._total_bytes = total_bytes
238        curr_start, curr_end = download._get_byte_range()
239        assert curr_start == 0
240        assert curr_end == total_bytes - 1
241        # Make sure this is less than the chunk size.
242        actual_size = curr_end - curr_start + 1
243        assert actual_size < chunk_size
244
245    @staticmethod
246    def _response_content_range(start_byte, end_byte, total_bytes):
247        return "bytes {:d}-{:d}/{:d}".format(start_byte, end_byte, total_bytes)
248
249    def _response_headers(self, start_byte, end_byte, total_bytes):
250        content_length = end_byte - start_byte + 1
251        resp_range = self._response_content_range(start_byte, end_byte, total_bytes)
252        return {
253            "content-length": "{:d}".format(content_length),
254            "content-range": resp_range,
255        }
256
257    def _mock_response(
258        self, start_byte, end_byte, total_bytes, content=None, status_code=None
259    ):
260        response_headers = self._response_headers(start_byte, end_byte, total_bytes)
261        return mock.Mock(
262            content=content,
263            headers=response_headers,
264            status_code=status_code,
265            spec=["content", "headers", "status_code"],
266        )
267
268    def test__prepare_request_already_finished(self):
269        download = _download.ChunkedDownload(EXAMPLE_URL, 64, None)
270        download._finished = True
271        with pytest.raises(ValueError) as exc_info:
272            download._prepare_request()
273
274        assert exc_info.match("Download has finished.")
275
276    def test__prepare_request_invalid(self):
277        download = _download.ChunkedDownload(EXAMPLE_URL, 64, None)
278        download._invalid = True
279        with pytest.raises(ValueError) as exc_info:
280            download._prepare_request()
281
282        assert exc_info.match("Download is invalid and cannot be re-used.")
283
284    def test__prepare_request(self):
285        chunk_size = 2048
286        download1 = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, None)
287        method1, url1, payload1, headers1 = download1._prepare_request()
288        assert method1 == "GET"
289        assert url1 == EXAMPLE_URL
290        assert payload1 is None
291        assert headers1 == {"range": "bytes=0-2047"}
292
293        download2 = _download.ChunkedDownload(
294            EXAMPLE_URL, chunk_size, None, start=19991
295        )
296        download2._total_bytes = 20101
297        method2, url2, payload2, headers2 = download2._prepare_request()
298        assert method2 == "GET"
299        assert url2 == EXAMPLE_URL
300        assert payload2 is None
301        assert headers2 == {"range": "bytes=19991-20100"}
302
303    def test__prepare_request_with_headers(self):
304        chunk_size = 2048
305        headers = {"patrizio": "Starf-ish"}
306        download = _download.ChunkedDownload(
307            EXAMPLE_URL, chunk_size, None, headers=headers
308        )
309        method, url, payload, new_headers = download._prepare_request()
310        assert method == "GET"
311        assert url == EXAMPLE_URL
312        assert payload is None
313        assert new_headers is headers
314        expected = {"patrizio": "Starf-ish", "range": "bytes=0-2047"}
315        assert headers == expected
316
317    def test__make_invalid(self):
318        download = _download.ChunkedDownload(EXAMPLE_URL, 512, None)
319        assert not download.invalid
320        download._make_invalid()
321        assert download.invalid
322
323    def test__process_response(self):
324        data = b"1234xyztL" * 37  # 9 * 37 == 33
325        chunk_size = len(data)
326        stream = io.BytesIO()
327        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream)
328        _fix_up_virtual(download)
329
330        already = 22
331        download._bytes_downloaded = already
332        total_bytes = 4444
333
334        # Check internal state before.
335        assert not download.finished
336        assert download.bytes_downloaded == already
337        assert download.total_bytes is None
338        # Actually call the method to update.
339        response = self._mock_response(
340            already,
341            already + chunk_size - 1,
342            total_bytes,
343            content=data,
344            status_code=int(http.client.PARTIAL_CONTENT),
345        )
346        download._process_response(response)
347        # Check internal state after.
348        assert not download.finished
349        assert download.bytes_downloaded == already + chunk_size
350        assert download.total_bytes == total_bytes
351        assert stream.getvalue() == data
352
353    def test__process_response_transfer_encoding(self):
354        data = b"1234xyztL" * 37
355        chunk_size = len(data)
356        stream = io.BytesIO()
357        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream)
358        _fix_up_virtual(download)
359
360        already = 22
361        download._bytes_downloaded = already
362        total_bytes = 4444
363
364        # Check internal state before.
365        assert not download.finished
366        assert download.bytes_downloaded == already
367        assert download.total_bytes is None
368        assert not download.invalid
369        # Actually call the method to update.
370        response = self._mock_response(
371            already,
372            already + chunk_size - 1,
373            total_bytes,
374            content=data,
375            status_code=int(http.client.PARTIAL_CONTENT),
376        )
377        response.headers["transfer-encoding"] = "chunked"
378        del response.headers["content-length"]
379        download._process_response(response)
380        # Check internal state after.
381        assert not download.finished
382        assert download.bytes_downloaded == already + chunk_size
383        assert download.total_bytes == total_bytes
384        assert stream.getvalue() == data
385
386    def test__process_response_bad_status(self):
387        chunk_size = 384
388        stream = mock.Mock(spec=["write"])
389        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream)
390        _fix_up_virtual(download)
391
392        total_bytes = 300
393
394        # Check internal state before.
395        assert not download.finished
396        assert download.bytes_downloaded == 0
397        assert download.total_bytes is None
398        # Actually call the method to update.
399        response = self._mock_response(
400            0, total_bytes - 1, total_bytes, status_code=int(http.client.NOT_FOUND)
401        )
402        with pytest.raises(common.InvalidResponse) as exc_info:
403            download._process_response(response)
404
405        error = exc_info.value
406        assert error.response is response
407        assert len(error.args) == 5
408        assert error.args[1] == response.status_code
409        assert error.args[3] == http.client.OK
410        assert error.args[4] == http.client.PARTIAL_CONTENT
411        # Check internal state after.
412        assert not download.finished
413        assert download.bytes_downloaded == 0
414        assert download.total_bytes is None
415        assert download.invalid
416        stream.write.assert_not_called()
417
418    def test__process_response_missing_content_length(self):
419        download = _download.ChunkedDownload(EXAMPLE_URL, 256, None)
420        _fix_up_virtual(download)
421
422        # Check internal state before.
423        assert not download.finished
424        assert download.bytes_downloaded == 0
425        assert download.total_bytes is None
426        assert not download.invalid
427        # Actually call the method to update.
428        response = mock.Mock(
429            headers={"content-range": "bytes 0-99/99"},
430            status_code=int(http.client.PARTIAL_CONTENT),
431            content=b"DEADBEEF",
432            spec=["headers", "status_code", "content"],
433        )
434        with pytest.raises(common.InvalidResponse) as exc_info:
435            download._process_response(response)
436
437        error = exc_info.value
438        assert error.response is response
439        assert len(error.args) == 2
440        assert error.args[1] == "content-length"
441        # Check internal state after.
442        assert not download.finished
443        assert download.bytes_downloaded == 0
444        assert download.total_bytes is None
445        assert download.invalid
446
447    def test__process_response_bad_content_range(self):
448        download = _download.ChunkedDownload(EXAMPLE_URL, 256, None)
449        _fix_up_virtual(download)
450
451        # Check internal state before.
452        assert not download.finished
453        assert download.bytes_downloaded == 0
454        assert download.total_bytes is None
455        assert not download.invalid
456        # Actually call the method to update.
457        data = b"stuff"
458        headers = {
459            "content-length": "{:d}".format(len(data)),
460            "content-range": "kites x-y/58",
461        }
462        response = mock.Mock(
463            content=data,
464            headers=headers,
465            status_code=int(http.client.PARTIAL_CONTENT),
466            spec=["content", "headers", "status_code"],
467        )
468        with pytest.raises(common.InvalidResponse) as exc_info:
469            download._process_response(response)
470
471        error = exc_info.value
472        assert error.response is response
473        assert len(error.args) == 3
474        assert error.args[1] == headers["content-range"]
475        # Check internal state after.
476        assert not download.finished
477        assert download.bytes_downloaded == 0
478        assert download.total_bytes is None
479        assert download.invalid
480
481    def test__process_response_body_wrong_length(self):
482        chunk_size = 10
483        stream = mock.Mock(spec=["write"])
484        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream)
485        _fix_up_virtual(download)
486
487        total_bytes = 100
488
489        # Check internal state before.
490        assert not download.finished
491        assert download.bytes_downloaded == 0
492        assert download.total_bytes is None
493        # Actually call the method to update.
494        data = b"not 10"
495        response = self._mock_response(
496            0,
497            chunk_size - 1,
498            total_bytes,
499            content=data,
500            status_code=int(http.client.PARTIAL_CONTENT),
501        )
502        with pytest.raises(common.InvalidResponse) as exc_info:
503            download._process_response(response)
504
505        error = exc_info.value
506        assert error.response is response
507        assert len(error.args) == 5
508        assert error.args[2] == chunk_size
509        assert error.args[4] == len(data)
510        # Check internal state after.
511        assert not download.finished
512        assert download.bytes_downloaded == 0
513        assert download.total_bytes is None
514        assert download.invalid
515        stream.write.assert_not_called()
516
517    def test__process_response_when_finished(self):
518        chunk_size = 256
519        stream = io.BytesIO()
520        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream)
521        _fix_up_virtual(download)
522
523        total_bytes = 200
524
525        # Check internal state before.
526        assert not download.finished
527        assert download.bytes_downloaded == 0
528        assert download.total_bytes is None
529        # Actually call the method to update.
530        data = b"abcd" * 50  # 4 * 50 == 200
531        response = self._mock_response(
532            0,
533            total_bytes - 1,
534            total_bytes,
535            content=data,
536            status_code=int(http.client.OK),
537        )
538        download._process_response(response)
539        # Check internal state after.
540        assert download.finished
541        assert download.bytes_downloaded == total_bytes
542        assert total_bytes < chunk_size
543        assert download.total_bytes == total_bytes
544        assert stream.getvalue() == data
545
546    def test__process_response_when_reaching_end(self):
547        chunk_size = 8192
548        end = 65000
549        stream = io.BytesIO()
550        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream, end=end)
551        _fix_up_virtual(download)
552
553        download._bytes_downloaded = 7 * chunk_size
554        download._total_bytes = 8 * chunk_size
555
556        # Check internal state before.
557        assert not download.finished
558        assert download.bytes_downloaded == 7 * chunk_size
559        assert download.total_bytes == 8 * chunk_size
560        # Actually call the method to update.
561        expected_size = end - 7 * chunk_size + 1
562        data = b"B" * expected_size
563        response = self._mock_response(
564            7 * chunk_size,
565            end,
566            8 * chunk_size,
567            content=data,
568            status_code=int(http.client.PARTIAL_CONTENT),
569        )
570        download._process_response(response)
571        # Check internal state after.
572        assert download.finished
573        assert download.bytes_downloaded == end + 1
574        assert download.bytes_downloaded < download.total_bytes
575        assert download.total_bytes == 8 * chunk_size
576        assert stream.getvalue() == data
577
578    def test__process_response_when_content_range_is_zero(self):
579        chunk_size = 10
580        stream = mock.Mock(spec=["write"])
581        download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream)
582        _fix_up_virtual(download)
583
584        content_range = _download._ZERO_CONTENT_RANGE_HEADER
585        headers = {"content-range": content_range}
586        status_code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE
587        response = mock.Mock(
588            headers=headers, status_code=status_code, spec=["headers", "status_code"]
589        )
590        download._process_response(response)
591        stream.write.assert_not_called()
592        assert download.finished
593        assert download.bytes_downloaded == 0
594        assert download.total_bytes is None
595
596    def test_consume_next_chunk(self):
597        download = _download.ChunkedDownload(EXAMPLE_URL, 256, None)
598        with pytest.raises(NotImplementedError) as exc_info:
599            download.consume_next_chunk(None)
600
601        exc_info.match("virtual")
602
603
604class Test__add_bytes_range(object):
605    def test_do_nothing(self):
606        headers = {}
607        ret_val = _download.add_bytes_range(None, None, headers)
608        assert ret_val is None
609        assert headers == {}
610
611    def test_both_vals(self):
612        headers = {}
613        ret_val = _download.add_bytes_range(17, 1997, headers)
614        assert ret_val is None
615        assert headers == {"range": "bytes=17-1997"}
616
617    def test_end_only(self):
618        headers = {}
619        ret_val = _download.add_bytes_range(None, 909, headers)
620        assert ret_val is None
621        assert headers == {"range": "bytes=0-909"}
622
623    def test_start_only(self):
624        headers = {}
625        ret_val = _download.add_bytes_range(3735928559, None, headers)
626        assert ret_val is None
627        assert headers == {"range": "bytes=3735928559-"}
628
629    def test_start_as_offset(self):
630        headers = {}
631        ret_val = _download.add_bytes_range(-123454321, None, headers)
632        assert ret_val is None
633        assert headers == {"range": "bytes=-123454321"}
634
635
636class Test_get_range_info(object):
637    @staticmethod
638    def _make_response(content_range):
639        headers = {"content-range": content_range}
640        return mock.Mock(headers=headers, spec=["headers"])
641
642    def _success_helper(self, **kwargs):
643        content_range = "Bytes 7-11/42"
644        response = self._make_response(content_range)
645        start_byte, end_byte, total_bytes = _download.get_range_info(
646            response, _get_headers, **kwargs
647        )
648        assert start_byte == 7
649        assert end_byte == 11
650        assert total_bytes == 42
651
652    def test_success(self):
653        self._success_helper()
654
655    def test_success_with_callback(self):
656        callback = mock.Mock(spec=[])
657        self._success_helper(callback=callback)
658        callback.assert_not_called()
659
660    def _failure_helper(self, **kwargs):
661        content_range = "nope x-6/y"
662        response = self._make_response(content_range)
663        with pytest.raises(common.InvalidResponse) as exc_info:
664            _download.get_range_info(response, _get_headers, **kwargs)
665
666        error = exc_info.value
667        assert error.response is response
668        assert len(error.args) == 3
669        assert error.args[1] == content_range
670
671    def test_failure(self):
672        self._failure_helper()
673
674    def test_failure_with_callback(self):
675        callback = mock.Mock(spec=[])
676        self._failure_helper(callback=callback)
677        callback.assert_called_once_with()
678
679    def _missing_header_helper(self, **kwargs):
680        response = mock.Mock(headers={}, spec=["headers"])
681        with pytest.raises(common.InvalidResponse) as exc_info:
682            _download.get_range_info(response, _get_headers, **kwargs)
683
684        error = exc_info.value
685        assert error.response is response
686        assert len(error.args) == 2
687        assert error.args[1] == "content-range"
688
689    def test_missing_header(self):
690        self._missing_header_helper()
691
692    def test_missing_header_with_callback(self):
693        callback = mock.Mock(spec=[])
694        self._missing_header_helper(callback=callback)
695        callback.assert_called_once_with()
696
697
698class Test__check_for_zero_content_range(object):
699    @staticmethod
700    def _make_response(content_range, status_code):
701        headers = {"content-range": content_range}
702        return mock.Mock(
703            headers=headers, status_code=status_code, spec=["headers", "status_code"]
704        )
705
706    def test_status_code_416_and_test_content_range_zero_both(self):
707        content_range = _download._ZERO_CONTENT_RANGE_HEADER
708        status_code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE
709        response = self._make_response(content_range, status_code)
710        assert _download._check_for_zero_content_range(
711            response, _get_status_code, _get_headers
712        )
713
714    def test_status_code_416_only(self):
715        content_range = "bytes 2-5/3"
716        status_code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE
717        response = self._make_response(content_range, status_code)
718        assert not _download._check_for_zero_content_range(
719            response, _get_status_code, _get_headers
720        )
721
722    def test_content_range_zero_only(self):
723        content_range = _download._ZERO_CONTENT_RANGE_HEADER
724        status_code = http.client.OK
725        response = self._make_response(content_range, status_code)
726        assert not _download._check_for_zero_content_range(
727            response, _get_status_code, _get_headers
728        )
729
730
731def _get_status_code(response):
732    return response.status_code
733
734
735def _get_headers(response):
736    return response.headers
737
738
739def _get_body(response):
740    return response.content
741
742
743def _fix_up_virtual(download):
744    download._get_status_code = _get_status_code
745    download._get_headers = _get_headers
746    download._get_body = _get_body
747
748
749def _check_retry_strategy(download):
750    retry_strategy = download._retry_strategy
751    assert isinstance(retry_strategy, common.RetryStrategy)
752    assert retry_strategy.max_sleep == common.MAX_SLEEP
753    assert retry_strategy.max_cumulative_retry == common.MAX_CUMULATIVE_RETRY
754    assert retry_strategy.max_retries is None
755