@@ -316,6 +316,112 @@ def test_download_with_checksum_mode_crc32(self):
316316 self .operations_called [1 ][1 ]['ChecksumMode' ], 'ENABLED'
317317 )
318318
319+ def test_mv_no_overwrite_flag_when_object_not_exists_on_target (self ):
320+ full_path = self .files .create_file ('foo.txt' , 'contents' )
321+ cmdline = f'{ self .prefix } { full_path } s3://bucket --no-overwrite'
322+ self .run_cmd (cmdline , expected_rc = 0 )
323+ # Verify putObject was called
324+ self .assertEqual (len (self .operations_called ), 1 )
325+ self .assertEqual (self .operations_called [0 ][0 ].name , 'PutObject' )
326+ # Verify the IfNoneMatch condition was set in the request
327+ self .assertEqual (self .operations_called [0 ][1 ]['IfNoneMatch' ], '*' )
328+ # Verify source file was deleted (move operation)
329+ self .assertFalse (os .path .exists (full_path ))
330+
331+ def test_mv_no_overwrite_flag_when_object_exists_on_target (self ):
332+ full_path = self .files .create_file ('foo.txt' , 'mycontent' )
333+ cmdline = (
334+ f'{ self .prefix } { full_path } s3://bucket/foo.txt --no-overwrite'
335+ )
336+ # Set up the response to simulate a PreconditionFailed error
337+ self .http_response .status_code = 412
338+ self .parsed_responses = [
339+ {
340+ 'Error' : {
341+ 'Code' : 'PreconditionFailed' ,
342+ 'Message' : 'At least one of the pre-conditions you specified did not hold' ,
343+ 'Condition' : 'If-None-Match' ,
344+ }
345+ }
346+ ]
347+ self .run_cmd (cmdline , expected_rc = 0 )
348+ # Verify PutObject was attempted with IfNoneMatch
349+ self .assertEqual (len (self .operations_called ), 1 )
350+ self .assertEqual (self .operations_called [0 ][0 ].name , 'PutObject' )
351+ self .assertEqual (self .operations_called [0 ][1 ]['IfNoneMatch' ], '*' )
352+ # Verify source file was not deleted
353+ self .assertTrue (os .path .exists (full_path ))
354+
355+ def test_mv_no_overwrite_flag_multipart_upload_when_object_not_exists_on_target (
356+ self ,
357+ ):
358+ # Create a large file that will trigger multipart upload
359+ full_path = self .files .create_file ('foo.txt' , 'a' * 10 * (1024 ** 2 ))
360+ cmdline = f'{ self .prefix } { full_path } s3://bucket --no-overwrite'
361+ # Set up responses for multipart upload
362+ self .parsed_responses = [
363+ {'UploadId' : 'foo' }, # CreateMultipartUpload response
364+ {'ETag' : '"foo-1"' }, # UploadPart response
365+ {'ETag' : '"foo-2"' }, # UploadPart response
366+ {}, # CompleteMultipartUpload response
367+ ]
368+ self .run_cmd (cmdline , expected_rc = 0 )
369+ # Verify all multipart operations were called
370+ self .assertEqual (len (self .operations_called ), 4 )
371+ self .assertEqual (
372+ self .operations_called [0 ][0 ].name , 'CreateMultipartUpload'
373+ )
374+ self .assertEqual (self .operations_called [1 ][0 ].name , 'UploadPart' )
375+ self .assertEqual (self .operations_called [2 ][0 ].name , 'UploadPart' )
376+ self .assertEqual (
377+ self .operations_called [3 ][0 ].name , 'CompleteMultipartUpload'
378+ )
379+ # Verify the IfNoneMatch condition was set in the CompleteMultipartUpload request
380+ self .assertEqual (self .operations_called [3 ][1 ]['IfNoneMatch' ], '*' )
381+ # Verify source file was deleted (successful move operation)
382+ self .assertFalse (os .path .exists (full_path ))
383+
384+ def test_mv_no_overwrite_flag_multipart_upload_when_object_exists_on_target (
385+ self ,
386+ ):
387+ # Create a large file that will trigger multipart upload
388+ full_path = self .files .create_file ('foo.txt' , 'a' * 10 * (1024 ** 2 ))
389+ cmdline = f'{ self .prefix } { full_path } s3://bucket --no-overwrite'
390+ # Set up responses for multipart upload
391+ self .parsed_responses = [
392+ {'UploadId' : 'foo' }, # CreateMultipartUpload response
393+ {'ETag' : '"foo-1"' }, # UploadPart response
394+ {'ETag' : '"foo-2"' }, # UploadPart response
395+ {
396+ 'Error' : {
397+ 'Code' : 'PreconditionFailed' ,
398+ 'Message' : 'At least one of the pre-conditions you specified did not hold' ,
399+ 'Condition' : 'If-None-Match' ,
400+ }
401+ }, # CompleteMultipartUpload response
402+ {}, # Abort Multipart
403+ ]
404+ self .run_cmd (cmdline , expected_rc = 0 )
405+ # Set up the response to simulate a PreconditionFailed error
406+ self .http_response .status_code = 412
407+ # Verify all multipart operations were called
408+ self .assertEqual (len (self .operations_called ), 5 )
409+ self .assertEqual (
410+ self .operations_called [0 ][0 ].name , 'CreateMultipartUpload'
411+ )
412+ self .assertEqual (self .operations_called [1 ][0 ].name , 'UploadPart' )
413+ self .assertEqual (self .operations_called [2 ][0 ].name , 'UploadPart' )
414+ self .assertEqual (
415+ self .operations_called [3 ][0 ].name , 'CompleteMultipartUpload'
416+ )
417+ self .assertEqual (
418+ self .operations_called [4 ][0 ].name , 'AbortMultipartUpload'
419+ )
420+ # Verify the IfNoneMatch condition was set in the CompleteMultipartUpload request
421+ self .assertEqual (self .operations_called [3 ][1 ]['IfNoneMatch' ], '*' )
422+ # Verify source file was not deleted (failed move operation due to PreconditionFailed)
423+ self .assertTrue (os .path .exists (full_path ))
424+
319425
320426class TestMvWithCRTClient (BaseCRTTransferClientTest ):
321427 def test_upload_move_using_crt_client (self ):
0 commit comments