@@ -40,17 +40,19 @@ const SAMPLE_DIGESTS = {
4040 sha256 : [ 'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=' , 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI=' ] ,
4141} ;
4242
43- // Every AWS-valid (algorithm, type) combination, plus the implicit default .
43+ // Every AWS-valid (algorithm, type) combination for an explicit-algorithm MPU .
4444// See validateChecksums.getChecksumDataFromMPUHeaders for the source of truth.
45+ // The implicit-default MPU (isDefault=true) is tested separately because AWS
46+ // rejects any per-part Checksum<X> field on a default MPU with InvalidPart,
47+ // regardless of value or algorithm.
4548const MATRIX = [
46- { algorithm : 'crc32' , type : 'COMPOSITE' , isDefault : false } ,
47- { algorithm : 'crc32' , type : 'FULL_OBJECT' , isDefault : false } ,
48- { algorithm : 'crc32c' , type : 'COMPOSITE' , isDefault : false } ,
49- { algorithm : 'crc32c' , type : 'FULL_OBJECT' , isDefault : false } ,
50- { algorithm : 'crc64nvme' , type : 'FULL_OBJECT' , isDefault : false } ,
51- { algorithm : 'crc64nvme' , type : 'FULL_OBJECT' , isDefault : true } ,
52- { algorithm : 'sha1' , type : 'COMPOSITE' , isDefault : false } ,
53- { algorithm : 'sha256' , type : 'COMPOSITE' , isDefault : false } ,
49+ { algorithm : 'crc32' , type : 'COMPOSITE' } ,
50+ { algorithm : 'crc32' , type : 'FULL_OBJECT' } ,
51+ { algorithm : 'crc32c' , type : 'COMPOSITE' } ,
52+ { algorithm : 'crc32c' , type : 'FULL_OBJECT' } ,
53+ { algorithm : 'crc64nvme' , type : 'FULL_OBJECT' } ,
54+ { algorithm : 'sha1' , type : 'COMPOSITE' } ,
55+ { algorithm : 'sha256' , type : 'COMPOSITE' } ,
5456] ;
5557
5658function makeStoredPart ( partNumber , checksum ) {
@@ -88,11 +90,11 @@ function pickWrongAlgo(algo) {
8890
8991describe ( 'validatePerPartChecksums' , ( ) => {
9092 describe ( 'AWS combination matrix' , ( ) => {
91- MATRIX . forEach ( ( { algorithm, type, isDefault } ) => {
92- const label = `${ algorithm } /${ type } ${ isDefault ? ' (default)' : '' } ` ;
93+ MATRIX . forEach ( ( { algorithm, type } ) => {
94+ const label = `${ algorithm } /${ type } ` ;
9395 const tag = TAG_BY_ALGO [ algorithm ] ;
9496 const [ d1 , d2 ] = SAMPLE_DIGESTS [ algorithm ] ;
95- const mpuChecksum = { algorithm, type, isDefault } ;
97+ const mpuChecksum = { algorithm, type, isDefault : false } ;
9698
9799 const stored = [ makeStoredPart ( 1 , { algorithm, value : d1 } ) , makeStoredPart ( 2 , { algorithm, value : d2 } ) ] ;
98100
@@ -143,7 +145,7 @@ describe('validatePerPartChecksums', () => {
143145 ) ;
144146 } ) ;
145147
146- const requiresPerPart = type === 'COMPOSITE' && ! isDefault ;
148+ const requiresPerPart = type === 'COMPOSITE' ;
147149 const missingLabel = requiresPerPart
148150 ? 'should return InvalidRequest when a part is missing its checksum'
149151 : 'should accept a parts list missing per-part checksums' ;
@@ -165,6 +167,60 @@ describe('validatePerPartChecksums', () => {
165167 } ) ;
166168 } ) ;
167169
170+ describe ( 'default MPU (isDefault=true)' , ( ) => {
171+ // AWS S3 rejects any per-part
172+ // Checksum<X> field on a default MPU with InvalidPart — even when
173+ // the field matches the implicit CRC64NVME algorithm and the value
174+ // is the same one the part was stored with.
175+ const mpuChecksum = { algorithm : 'crc64nvme' , type : 'FULL_OBJECT' , isDefault : true } ;
176+ const [ d1 , d2 ] = SAMPLE_DIGESTS . crc64nvme ;
177+ const stored = [
178+ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : d1 } ) ,
179+ makeStoredPart ( 2 , { algorithm : 'crc64nvme' , value : d2 } ) ,
180+ ] ;
181+ const invalidPartMessage =
182+ 'One or more of the specified parts could not be ' +
183+ 'found. The part may not have been uploaded, or ' +
184+ 'the specified entity tag may not match the ' +
185+ "part's entity tag." ;
186+
187+ it ( 'should accept when no parts include a checksum field' , ( ) => {
188+ const jsonList = { Part : [ makeJsonPart ( 1 , 'etag1' ) , makeJsonPart ( 2 , 'etag2' ) ] } ;
189+ const err = validatePerPartChecksums ( jsonList , stored , SPLITTER , mpuChecksum ) ;
190+ assert . strictEqual ( err , null ) ;
191+ } ) ;
192+
193+ it ( 'should return InvalidPart when a part includes the matching field (correct value)' , ( ) => {
194+ const jsonList = {
195+ Part : [ makeJsonPart ( 1 , 'etag1' , { ChecksumCRC64NVME : d1 } ) , makeJsonPart ( 2 , 'etag2' ) ] ,
196+ } ;
197+ const err = validatePerPartChecksums ( jsonList , stored , SPLITTER , mpuChecksum ) ;
198+ assert ( err ) ;
199+ assert . strictEqual ( err . is . InvalidPart , true ) ;
200+ assert . strictEqual ( err . description , invalidPartMessage ) ;
201+ } ) ;
202+
203+ it ( 'should return InvalidPart when a part includes the matching field (wrong value)' , ( ) => {
204+ const jsonList = {
205+ Part : [ makeJsonPart ( 1 , 'etag1' , { ChecksumCRC64NVME : d2 } ) , makeJsonPart ( 2 , 'etag2' ) ] ,
206+ } ;
207+ const err = validatePerPartChecksums ( jsonList , stored , SPLITTER , mpuChecksum ) ;
208+ assert ( err ) ;
209+ assert . strictEqual ( err . is . InvalidPart , true ) ;
210+ assert . strictEqual ( err . description , invalidPartMessage ) ;
211+ } ) ;
212+
213+ it ( 'should return InvalidPart when a part includes a non-matching algorithm field' , ( ) => {
214+ const jsonList = {
215+ Part : [ makeJsonPart ( 1 , 'etag1' , { ChecksumCRC32 : SAMPLE_DIGESTS . crc32 [ 0 ] } ) , makeJsonPart ( 2 , 'etag2' ) ] ,
216+ } ;
217+ const err = validatePerPartChecksums ( jsonList , stored , SPLITTER , mpuChecksum ) ;
218+ assert ( err ) ;
219+ assert . strictEqual ( err . is . InvalidPart , true ) ;
220+ assert . strictEqual ( err . description , invalidPartMessage ) ;
221+ } ) ;
222+ } ) ;
223+
168224 describe ( 'legacy MPU (no algorithm configured)' , ( ) => {
169225 // Pre-feature MPUs have storedMetadata.checksumAlgorithm === undefined.
170226 // Pre-PR CompleteMPU silently ignored any per-part Checksum<X> body
0 commit comments