@@ -348,3 +348,136 @@ def test_server_dop_cap(tmp_path):
348348 # and due to the server DoP cap, each of them will have a thread count
349349 # of 1.
350350 assert len (list (filter (lambda e : e .args == (1 ,), tpe .call_args_list ))) == 3
351+
352+
353+ def _setup_test_for_reraise_file_transfer_work_fn_error (tmp_path , reraise_param_value ):
354+ """Helper function to set up common test infrastructure for tests related to re-raising file transfer work function error.
355+
356+ Returns:
357+ tuple: (agent, test_exception, mock_client, mock_create_client)
358+ """
359+
360+ file1 = tmp_path / "file1"
361+ file1 .write_text ("test content" )
362+
363+ # Mock cursor with connection attribute
364+ mock_cursor = mock .MagicMock (autospec = SnowflakeCursor )
365+ mock_cursor .connection ._reraise_error_in_file_transfer_work_function = (
366+ reraise_param_value
367+ )
368+
369+ # Create file transfer agent
370+ agent = SnowflakeFileTransferAgent (
371+ mock_cursor ,
372+ "PUT some_file.txt" ,
373+ {
374+ "data" : {
375+ "command" : "UPLOAD" ,
376+ "src_locations" : [str (file1 )],
377+ "sourceCompression" : "none" ,
378+ "parallel" : 1 ,
379+ "stageInfo" : {
380+ "creds" : {
381+ "AZURE_SAS_TOKEN" : "sas_token" ,
382+ },
383+ "location" : "some_bucket" ,
384+ "region" : "no_region" ,
385+ "locationType" : "AZURE" ,
386+ "path" : "remote_loc" ,
387+ "endPoint" : "" ,
388+ "storageAccount" : "storage_account" ,
389+ },
390+ },
391+ "success" : True ,
392+ },
393+ reraise_error_in_file_transfer_work_function = reraise_param_value ,
394+ )
395+
396+ # Quick check to make sure the field _reraise_error_in_file_transfer_work_function is correctly populated
397+ assert (
398+ agent ._reraise_error_in_file_transfer_work_function == reraise_param_value
399+ ), f"expected { reraise_param_value } , got { agent ._reraise_error_in_file_transfer_work_function } "
400+
401+ # Parse command and initialize file metadata
402+ agent ._parse_command ()
403+ agent ._init_file_metadata ()
404+ agent ._process_file_compression_type ()
405+
406+ # Create a custom exception to be raised by the work function
407+ test_exception = Exception ("Test work function failure" )
408+
409+ def mock_upload_chunk_with_delay (* args , ** kwargs ):
410+ import time
411+
412+ time .sleep (0.2 )
413+ raise test_exception
414+
415+ # Set up mock client patch, which we will activate in each unit test case.
416+ mock_create_client = mock .patch .object (agent , "_create_file_transfer_client" )
417+ mock_client = mock .MagicMock ()
418+ mock_client .upload_chunk .side_effect = mock_upload_chunk_with_delay
419+
420+ # Set up mock client attributes needed for the transfer flow
421+ mock_client .meta = agent ._file_metadata [0 ]
422+ mock_client .num_of_chunks = 1
423+ mock_client .successful_transfers = 0
424+ mock_client .failed_transfers = 0
425+ mock_client .lock = mock .MagicMock ()
426+ # Mock methods that would be called during cleanup
427+ mock_client .finish_upload = mock .MagicMock ()
428+ mock_client .delete_client_data = mock .MagicMock ()
429+
430+ return agent , test_exception , mock_client , mock_create_client
431+
432+
433+ # Skip for old drivers because the connection config of
434+ # reraise_error_in_file_transfer_work_function is newly introduced.
435+ @pytest .mark .skipolddriver
436+ def test_python_reraise_file_transfer_work_fn_error_as_is (tmp_path ):
437+ """Tests that when reraise_error_in_file_transfer_work_function config is True,
438+ exceptions are reraised immediately without continuing execution after transfer().
439+ """
440+ agent , test_exception , mock_client , mock_create_client_patch = (
441+ _setup_test_for_reraise_file_transfer_work_fn_error (tmp_path , True )
442+ )
443+
444+ with mock_create_client_patch as mock_create_client :
445+ mock_create_client .return_value = mock_client
446+
447+ # Test that with the connection config
448+ # reraise_error_in_file_transfer_work_function is True, the
449+ # exception is reraised immediately in main thread of transfer.
450+ with pytest .raises (Exception ) as exc_info :
451+ agent .transfer (agent ._file_metadata )
452+
453+ # Verify it's the same exception we injected
454+ assert exc_info .value is test_exception
455+
456+ # Verify that prepare_upload was called (showing the work function was executed)
457+ mock_client .prepare_upload .assert_called_once ()
458+
459+
460+ # Skip for old drivers because the connection config of
461+ # reraise_error_in_file_transfer_work_function is newly introduced.
462+ @pytest .mark .skipolddriver
463+ def test_python_not_reraise_file_transfer_work_fn_error_as_is (tmp_path ):
464+ """Tests that when reraise_error_in_file_transfer_work_function config is False (default),
465+ where exceptions are stored in file metadata but execution continues.
466+ """
467+ agent , test_exception , mock_client , mock_create_client_patch = (
468+ _setup_test_for_reraise_file_transfer_work_fn_error (tmp_path , False )
469+ )
470+
471+ with mock_create_client_patch as mock_create_client :
472+ mock_create_client .return_value = mock_client
473+
474+ # Verify that with the connection config
475+ # reraise_error_in_file_transfer_work_function is False, the
476+ # exception is not reraised (but instead stored in file metadata).
477+ agent .transfer (agent ._file_metadata )
478+
479+ # Verify that the error was stored in the file metadata
480+ assert agent ._file_metadata [0 ].error_details is test_exception
481+
482+ # Verify that prepare_upload was called
483+ mock_client .prepare_upload .assert_called_once ()
0 commit comments