@@ -348,3 +348,136 @@ def test_server_dop_cap(tmp_path):
348
348
# and due to the server DoP cap, each of them will have a thread count
349
349
# of 1.
350
350
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