|
1 | 1 | from functools import partial |
2 | | -from io import IOBase, BytesIO |
| 2 | +from io import IOBase, BytesIO, SEEK_END |
3 | 3 | from numbers import Number |
4 | 4 | import os |
5 | 5 | from unittest.mock import MagicMock, Mock, PropertyMock, call, patch, ANY |
|
8 | 8 | SSLError, |
9 | 9 | ConnectionError as RequestsConnectionError, |
10 | 10 | ) |
11 | | -from requests_toolbelt import MultipartEncoder |
12 | | - |
13 | 11 | import pytest |
14 | 12 |
|
15 | 13 | from boxsdk import CCGAuth |
|
19 | 17 | from boxsdk.network.default_network import DefaultNetwork, DefaultNetworkResponse |
20 | 18 | from boxsdk.session.box_response import BoxResponse |
21 | 19 | from boxsdk.session.session import Session, Translator, AuthorizedSession |
| 20 | +from boxsdk.util.multipart_stream import MultipartStream |
22 | 21 |
|
23 | 22 |
|
24 | 23 | @pytest.fixture(scope='function', params=[False, True]) |
@@ -271,12 +270,10 @@ def test_box_session_seeks_file_after_retry( |
271 | 270 | assert box_response.ok == generic_successful_response.ok |
272 | 271 | mock_file_1.tell.assert_called_with() |
273 | 272 | mock_file_2.tell.assert_called_with() |
274 | | - mock_file_1.seek.assert_called_with(0) |
275 | | - assert mock_file_1.seek.call_count == 2 |
276 | | - mock_file_1.seek.assert_has_calls([call(0), call(0)]) |
277 | | - mock_file_2.seek.assert_called_with(3) |
278 | | - assert mock_file_2.seek.call_count == 2 |
279 | | - mock_file_2.seek.assert_has_calls([call(3), call(3)]) |
| 273 | + # before each attempt the session rewinds the stream, then the multipart |
| 274 | + # encoder measures its size and restores the position |
| 275 | + assert mock_file_1.seek.call_args_list == [call(0), call(0, SEEK_END), call(0)] * 2 |
| 276 | + assert mock_file_2.seek.call_args_list == [call(3), call(0, SEEK_END), call(3)] * 2 |
280 | 277 |
|
281 | 278 |
|
282 | 279 | def test_box_session_raises_for_non_json_response( |
@@ -645,7 +642,14 @@ def test_multipart_request_with_enabled_streaming_file_content( |
645 | 642 | assert call_args[1] == test_url |
646 | 643 | assert call_kwargs['access_token'] == 'fake_access_token' |
647 | 644 | assert call_kwargs['log_response_content'] is True |
648 | | - assert isinstance(call_kwargs['data'], MultipartEncoder) |
649 | | - assert call_kwargs['data'].fields['attributes'] == '{"name": "test_file"}' |
650 | | - assert call_kwargs['data'].fields['file'][0] == 'unused' |
651 | | - assert isinstance(call_kwargs['data'].fields['file'][1], BytesIO) |
| 645 | + multipart_stream = call_kwargs['data'] |
| 646 | + assert isinstance(multipart_stream, MultipartStream) |
| 647 | + assert call_kwargs['headers']['Content-Type'] == multipart_stream.content_type |
| 648 | + body = multipart_stream.read() |
| 649 | + assert ( |
| 650 | + b'name="attributes"\r\n\r\n{"name": "test_file"}\r\n' |
| 651 | + + f'--{multipart_stream.boundary}\r\n'.encode() |
| 652 | + + b'Content-Disposition: form-data; name="file"; filename="unused"\r\n\r\n' |
| 653 | + + file_bytes |
| 654 | + + b'\r\n' |
| 655 | + ) in body |
0 commit comments