2020
2121from botocore .exceptions import ClientError
2222from s3transfer .compat import SOCKET_ERROR
23- from s3transfer .exceptions import RetriesExceededError , S3DownloadFailedError
23+ from s3transfer .exceptions import (
24+ RetriesExceededError ,
25+ S3DownloadFailedError ,
26+ S3ValidationError ,
27+ )
2428from s3transfer .manager import TransferConfig , TransferManager
2529
2630from tests import (
@@ -109,7 +113,7 @@ def add_head_object_response(self, expected_params=None):
109113 self .stubber .add_response (** head_response )
110114
111115 def add_successful_get_object_responses (
112- self , expected_params = None , expected_ranges = None
116+ self , expected_params = None , expected_ranges = None , extras = None
113117 ):
114118 # Add all get_object responses needed to complete the download.
115119 # Should account for both ranged and nonranged downloads.
@@ -124,6 +128,8 @@ def add_successful_get_object_responses(
124128 stubbed_response ['expected_params' ]['Range' ] = (
125129 expected_ranges [i ]
126130 )
131+ if extras :
132+ stubbed_response ['service_response' ].update (extras [i ])
127133 self .stubber .add_response (** stubbed_response )
128134
129135 def add_n_retryable_get_object_responses (self , n , num_reads = 0 ):
@@ -511,9 +517,12 @@ def test_download(self):
511517 'RequestPayer' : 'requester' ,
512518 }
513519 expected_ranges = ['bytes=0-3' , 'bytes=4-7' , 'bytes=8-' ]
520+ stubbed_ranges = ['bytes 0-3/10' , 'bytes 4-7/10' , 'bytes 8-9/10' ]
514521 self .add_head_object_response (expected_params )
515522 self .add_successful_get_object_responses (
516- {** expected_params , 'IfMatch' : self .etag }, expected_ranges
523+ {** expected_params , 'IfMatch' : self .etag },
524+ expected_ranges ,
525+ [{"ContentRange" : r } for r in stubbed_ranges ],
517526 )
518527
519528 future = self .manager .download (
@@ -547,6 +556,28 @@ def test_download_with_checksum_enabled(self):
547556 with open (self .filename , 'rb' ) as f :
548557 self .assertEqual (self .content , f .read ())
549558
559+ def test_download_raises_if_content_range_mismatch (self ):
560+ expected_params = {
561+ 'Bucket' : self .bucket ,
562+ 'Key' : self .key ,
563+ }
564+ expected_ranges = ['bytes=0-3' , 'bytes=4-7' , 'bytes=8-' ]
565+ # Note that the final retrieved range should be `bytes 8-9/10`.
566+ stubbed_ranges = ['bytes 0-3/10' , 'bytes 4-7/10' , 'bytes 7-8/10' ]
567+ self .add_head_object_response (expected_params )
568+ self .add_successful_get_object_responses (
569+ {** expected_params , 'IfMatch' : self .etag },
570+ expected_ranges ,
571+ [{"ContentRange" : r } for r in stubbed_ranges ],
572+ )
573+
574+ future = self .manager .download (
575+ self .bucket , self .key , self .filename , self .extra_args
576+ )
577+ with self .assertRaises (S3ValidationError ) as e :
578+ future .result ()
579+ self .assertIn ('does not match content range' , str (e .exception ))
580+
550581 def test_download_raises_if_etag_validation_fails (self ):
551582 expected_params = {
552583 'Bucket' : self .bucket ,
0 commit comments