8
8
from datetime import datetime , timedelta
9
9
from itertools import product
10
10
from typing import Any , Dict , List , Optional , Set
11
- from unittest .mock import patch
11
+ from unittest .mock import MagicMock , patch
12
12
13
13
import pytest
14
14
from airbyte_cdk .sources .file_based .config .abstract_file_based_spec import AbstractFileBasedSpec
15
15
from airbyte_cdk .sources .file_based .exceptions import ErrorListingFiles , FileBasedSourceError
16
16
from airbyte_cdk .sources .file_based .file_based_stream_reader import FileReadMode
17
17
from airbyte_cdk .sources .file_based .remote_file import RemoteFile
18
18
from botocore .stub import Stubber
19
- from moto import mock_sts
19
+ from moto import mock_s3 , mock_sts
20
20
from pydantic import AnyUrl
21
21
from source_s3 .v4 .config import Config
22
22
from source_s3 .v4 .stream_reader import SourceS3StreamReader
@@ -124,10 +124,51 @@ def test_get_matching_files(
124
124
except Exception as exc :
125
125
raise exc
126
126
127
- stub = set_stub (reader , mocked_response , multiple_pages )
128
- files = list (reader .get_matching_files (globs , None , logger ))
129
- stub .deactivate ()
130
- assert set (f .uri for f in files ) == expected_uris
127
+ with patch .object (SourceS3StreamReader , 's3_client' , new_callable = MagicMock ) as mock_s3_client :
128
+ _setup_mock_s3_client (mock_s3_client , mocked_response , multiple_pages )
129
+ files = list (reader .get_matching_files (globs , None , logger ))
130
+ assert set (f .uri for f in files ) == expected_uris
131
+
132
+
133
+ def _setup_mock_s3_client (mock_s3_client , mocked_response , multiple_pages ):
134
+ responses = []
135
+ if multiple_pages and len (mocked_response ) > 1 :
136
+ # Split the mocked_response for pagination simulation
137
+ first_half = mocked_response [:len (mocked_response ) // 2 ]
138
+ second_half = mocked_response [len (mocked_response ) // 2 :]
139
+
140
+ responses .append ({
141
+ "IsTruncated" : True ,
142
+ "Contents" : first_half ,
143
+ "KeyCount" : len (first_half ),
144
+ "NextContinuationToken" : "token" ,
145
+ })
146
+
147
+ responses .append ({
148
+ "IsTruncated" : False ,
149
+ "Contents" : second_half ,
150
+ "KeyCount" : len (second_half ),
151
+ })
152
+ else :
153
+ responses .append ({
154
+ "IsTruncated" : False ,
155
+ "Contents" : mocked_response ,
156
+ "KeyCount" : len (mocked_response ),
157
+ })
158
+
159
+ def list_objects_v2_side_effect (Bucket , Prefix = None , ContinuationToken = None , ** kwargs ):
160
+ if ContinuationToken == "token" :
161
+ return responses [1 ]
162
+ return responses [0 ]
163
+
164
+ mock_s3_client .list_objects_v2 = MagicMock (side_effect = list_objects_v2_side_effect )
165
+
166
+
167
+ def _split_mocked_response (mocked_response , multiple_pages ):
168
+ if not multiple_pages :
169
+ return mocked_response , []
170
+ split_index = len (mocked_response ) // 2
171
+ return mocked_response [:split_index ], mocked_response [split_index :]
131
172
132
173
133
174
@patch ("boto3.client" )
@@ -196,9 +237,9 @@ def test_open_file_calls_any_open_with_the_right_encoding(smart_open_mock):
196
237
with reader .open_file (RemoteFile (uri = "" , last_modified = datetime .now ()), FileReadMode .READ , encoding , logger ) as fp :
197
238
fp .read ()
198
239
199
- smart_open_mock .assert_called_once_with (
200
- "s3://test/" , transport_params = { "client" : reader . s3_client }, mode = FileReadMode .READ .value , encoding = encoding
201
- )
240
+ assert smart_open_mock .call_args . args == ( "s3://test/" ,)
241
+ assert smart_open_mock . call_args . kwargs [ "mode" ] == FileReadMode .READ .value
242
+ assert smart_open_mock . call_args . kwargs [ "encoding" ] == encoding
202
243
203
244
204
245
def test_get_s3_client_without_config_raises_exception ():
@@ -218,29 +259,6 @@ def documentation_url(cls) -> AnyUrl:
218
259
stream_reader .config = other_config
219
260
220
261
221
- def set_stub (reader : SourceS3StreamReader , contents : List [Dict [str , Any ]], multiple_pages : bool ) -> Stubber :
222
- s3_stub = Stubber (reader .s3_client )
223
- split_contents_idx = int (len (contents ) / 2 ) if multiple_pages else - 1
224
- page1 , page2 = contents [:split_contents_idx ], contents [split_contents_idx :]
225
- resp = {
226
- "KeyCount" : len (page1 ),
227
- "Contents" : page1 ,
228
- }
229
- if page2 :
230
- resp ["NextContinuationToken" ] = "token"
231
- s3_stub .add_response ("list_objects_v2" , resp )
232
- if page2 :
233
- s3_stub .add_response (
234
- "list_objects_v2" ,
235
- {
236
- "KeyCount" : len (page2 ),
237
- "Contents" : page2 ,
238
- },
239
- )
240
- s3_stub .activate ()
241
- return s3_stub
242
-
243
-
244
262
@mock_sts
245
263
@patch ("source_s3.v4.stream_reader.boto3.client" )
246
264
def test_get_iam_s3_client (boto3_client_mock ):
@@ -303,4 +321,4 @@ def test_filter_file_by_start_date(start_date: datetime, last_modified_date: dat
303
321
start_date = start_date .strftime ("%Y-%m-%dT%H:%M:%SZ" )
304
322
)
305
323
306
- assert expected_result == reader .is_modified_after_start_date (last_modified_date )
324
+ assert expected_result == reader .is_modified_after_start_date (last_modified_date )
0 commit comments