@@ -255,6 +255,69 @@ def test_copy_with_extra_args(self):
255255 future .result ()
256256 self .stubber .assert_no_pending_responses ()
257257
258+ def test_copy_with_ifnonematch_when_object_not_exists_at_target (self ):
259+ self .extra_args ['IfNoneMatch' ] = '*'
260+
261+ expected_head_params = {
262+ 'Bucket' : 'mysourcebucket' ,
263+ 'Key' : 'mysourcekey' ,
264+ }
265+
266+ expected_copy_object = {
267+ 'Bucket' : 'mybucket' ,
268+ 'Key' : 'mykey' ,
269+ 'CopySource' : {
270+ 'Bucket' : 'mysourcebucket' ,
271+ 'Key' : 'mysourcekey' ,
272+ },
273+ "IfNoneMatch" : "*"
274+ }
275+
276+ self .add_head_object_response (expected_params = expected_head_params )
277+ self .add_successful_copy_responses (
278+ expected_copy_params = expected_copy_object
279+ )
280+
281+ call_kwargs = self .create_call_kwargs ()
282+ call_kwargs ['extra_args' ] = self .extra_args
283+ future = self .manager .copy (** call_kwargs )
284+ future .result ()
285+ self .stubber .assert_no_pending_responses ()
286+
287+ def test_copy_with_ifnonematch_when_object_exists_at_target (self ):
288+ self .extra_args ['IfNoneMatch' ] = '*'
289+
290+ expected_head_params = {
291+ 'Bucket' : 'mysourcebucket' ,
292+ 'Key' : 'mysourcekey' ,
293+ }
294+
295+ self .add_head_object_response (expected_params = expected_head_params )
296+
297+ # Mock a PreconditionFailed error for copy_object
298+ self .stubber .add_client_error (
299+ method = 'copy_object' ,
300+ service_error_code = 'PreconditionFailed' ,
301+ service_message = 'The condition specified in the conditional header(s) was not met' ,
302+ http_status_code = 412 ,
303+ expected_params = {
304+ 'Bucket' : self .bucket ,
305+ 'Key' : self .key ,
306+ 'CopySource' : self .copy_source ,
307+ 'IfNoneMatch' : '*' ,
308+ },
309+ )
310+
311+ call_kwargs = self .create_call_kwargs ()
312+ call_kwargs ['extra_args' ] = self .extra_args
313+ future = self .manager .copy (** call_kwargs )
314+ with self .assertRaises (ClientError ) as context :
315+ future .result ()
316+ self .assertEqual (
317+ context .exception .response ['Error' ]['Code' ], 'PreconditionFailed'
318+ )
319+ self .stubber .assert_no_pending_responses ()
320+
258321 def test_copy_maps_extra_args_to_head_object (self ):
259322 self .extra_args ['CopySourceSSECustomerAlgorithm' ] = 'AES256'
260323
@@ -719,135 +782,6 @@ def test_mp_copy_with_tagging_directive(self):
719782 future .result ()
720783 self .stubber .assert_no_pending_responses ()
721784
722- def test_copy_with_no_overwrite_flag_when_small_object_exists_at_target (
723- self ,
724- ):
725- # Set up IfNoneMatch in extra_args
726- self .extra_args ['IfNoneMatch' ] = '*'
727- # Setting up the size of object
728- small_content_size = 5
729- self .content = b'0' * small_content_size
730- # Add head object response with small content size
731- head_response = self .create_stubbed_responses ()[0 ]
732- head_response ['service_response' ] = {
733- 'ContentLength' : small_content_size
734- }
735- self .stubber .add_response (** head_response )
736- # Should use multipart upload
737- # Add create_multipart_upload response
738- self .stubber .add_response (
739- 'create_multipart_upload' ,
740- service_response = {'UploadId' : self .multipart_id },
741- expected_params = {
742- 'Bucket' : self .bucket ,
743- 'Key' : self .key ,
744- },
745- )
746- # Add upload_part_copy response
747- self .stubber .add_response (
748- 'upload_part_copy' ,
749- {'CopyPartResult' : {'ETag' : 'etag-1' }},
750- {
751- 'Bucket' : self .bucket ,
752- 'Key' : self .key ,
753- 'CopySource' : self .copy_source ,
754- 'UploadId' : self .multipart_id ,
755- 'PartNumber' : 1 ,
756- 'CopySourceRange' : f'bytes=0-{ small_content_size - 1 } ' ,
757- },
758- )
759- # Mock a PreconditionFailed error for complete_multipart_upload
760- self .stubber .add_client_error (
761- method = 'complete_multipart_upload' ,
762- service_error_code = 'PreconditionFailed' ,
763- service_message = 'The condition specified in the conditional header(s) was not met' ,
764- http_status_code = 412 ,
765- expected_params = {
766- 'Bucket' : self .bucket ,
767- 'Key' : self .key ,
768- 'UploadId' : self .multipart_id ,
769- 'MultipartUpload' : {
770- 'Parts' : [{'ETag' : 'etag-1' , 'PartNumber' : 1 }]
771- },
772- 'IfNoneMatch' : '*' ,
773- },
774- )
775- # Add abort_multipart_upload response
776- self .stubber .add_response (
777- 'abort_multipart_upload' ,
778- service_response = {},
779- expected_params = {
780- 'Bucket' : self .bucket ,
781- 'Key' : self .key ,
782- 'UploadId' : self .multipart_id ,
783- },
784- )
785- call_kwargs = self .create_call_kwargs ()
786- call_kwargs ['extra_args' ] = self .extra_args
787- future = self .manager .copy (** call_kwargs )
788- with self .assertRaises (ClientError ) as context :
789- future .result ()
790- self .assertEqual (
791- context .exception .response ['Error' ]['Code' ], 'PreconditionFailed'
792- )
793- self .stubber .assert_no_pending_responses ()
794-
795- def test_copy_with_no_overwrite_flag_when_small_object_not_exists_at_target (
796- self ,
797- ):
798- # Set up IfNoneMatch in extra_args
799- self .extra_args ['IfNoneMatch' ] = '*'
800- # Setting up the size of object
801- small_content_size = 5
802- self .content = b'0' * small_content_size
803- # Add head object response with small content size
804- head_response = self .create_stubbed_responses ()[0 ]
805- head_response ['service_response' ] = {
806- 'ContentLength' : small_content_size
807- }
808- self .stubber .add_response (** head_response )
809- # Should use multipart copy
810- # Add create_multipart_upload response
811- self .stubber .add_response (
812- 'create_multipart_upload' ,
813- service_response = {'UploadId' : self .multipart_id },
814- expected_params = {
815- 'Bucket' : self .bucket ,
816- 'Key' : self .key ,
817- },
818- )
819- # Add upload_part_copy response
820- self .stubber .add_response (
821- 'upload_part_copy' ,
822- {'CopyPartResult' : {'ETag' : 'etag-1' }},
823- {
824- 'Bucket' : self .bucket ,
825- 'Key' : self .key ,
826- 'CopySource' : self .copy_source ,
827- 'UploadId' : self .multipart_id ,
828- 'PartNumber' : 1 ,
829- 'CopySourceRange' : f'bytes=0-{ small_content_size - 1 } ' ,
830- },
831- )
832- self .stubber .add_response (
833- 'complete_multipart_upload' ,
834- service_response = {},
835- expected_params = {
836- 'Bucket' : self .bucket ,
837- 'Key' : self .key ,
838- 'UploadId' : self .multipart_id ,
839- 'MultipartUpload' : {
840- 'Parts' : [{'ETag' : 'etag-1' , 'PartNumber' : 1 }]
841- },
842- 'IfNoneMatch' : '*' ,
843- },
844- )
845- call_kwargs = self .create_call_kwargs ()
846- call_kwargs ['extra_args' ] = self .extra_args
847- future = self .manager .copy (** call_kwargs )
848- future .result ()
849- self .stubber .assert_no_pending_responses ()
850-
851785 def test_copy_with_no_overwrite_flag_when_large_object_exists_at_target (
852786 self ,
853787 ):
@@ -931,6 +865,7 @@ def test_copy_with_no_overwrite_flag_when_large_object_not_exists_at_target(
931865 future = self .manager .copy (** call_kwargs )
932866 future .result ()
933867 self .stubber .assert_no_pending_responses ()
868+
934869 def test_copy_fails_if_etag_validation_fails (self ):
935870 expected_params = {
936871 'Bucket' : 'mybucket' ,
0 commit comments