@@ -4201,13 +4201,23 @@ describe('computeFinalChecksum', () => {
42014201 } ) ) ;
42024202 }
42034203
4204- it ( 'should return null when MPU has no checksumAlgorithm' , async ( ) => {
4204+ function assertSoftNull ( got ) {
4205+ assert . deepStrictEqual ( got , { result : null , error : null } ) ;
4206+ }
4207+
4208+ function assertInternalError ( got ) {
4209+ assert . strictEqual ( got . result , null ) ;
4210+ assert ( got . error , 'expected an error on the result' ) ;
4211+ assert . strictEqual ( got . error . is . InternalError , true ) ;
4212+ }
4213+
4214+ it ( 'should return { result: null, error: null } when MPU has no checksumAlgorithm' , async ( ) => {
42054215 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
42064216 const got = await computeFinalChecksum ( stored , partListFromStored ( stored ) , { } , splitter , uploadId , log ) ;
4207- assert . strictEqual ( got , null ) ;
4217+ assertSoftNull ( got ) ;
42084218 } ) ;
42094219
4210- it ( 'should return null when MPU has no checksumType' , async ( ) => {
4220+ it ( 'should return { result: null, error: null } when MPU has no checksumType' , async ( ) => {
42114221 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
42124222 const got = await computeFinalChecksum (
42134223 stored ,
@@ -4217,7 +4227,7 @@ describe('computeFinalChecksum', () => {
42174227 uploadId ,
42184228 log ,
42194229 ) ;
4220- assert . strictEqual ( got , null ) ;
4230+ assertSoftNull ( got ) ;
42214231 } ) ;
42224232
42234233 it ( 'should return COMPOSITE checksum with -N suffix for SHA256 MPU' , async ( ) => {
@@ -4227,25 +4237,26 @@ describe('computeFinalChecksum', () => {
42274237 makeStoredPart ( 2 , { algorithm : 'sha256' , value : d2 } ) ,
42284238 makeStoredPart ( 3 , { algorithm : 'sha256' , value : d3 } ) ,
42294239 ] ;
4230- const got = await computeFinalChecksum (
4240+ const { result , error } = await computeFinalChecksum (
42314241 stored ,
42324242 partListFromStored ( stored ) ,
42334243 { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
42344244 splitter ,
42354245 uploadId ,
42364246 log ,
42374247 ) ;
4238- assert ( got ) ;
4239- assert . strictEqual ( got . algorithm , 'sha256' ) ;
4240- assert . strictEqual ( got . type , 'COMPOSITE' ) ;
4241- assert ( got . value . endsWith ( '-3' ) , `expected -N suffix, got ${ got . value } ` ) ;
4248+ assert . strictEqual ( error , null ) ;
4249+ assert ( result ) ;
4250+ assert . strictEqual ( result . algorithm , 'sha256' ) ;
4251+ assert . strictEqual ( result . type , 'COMPOSITE' ) ;
4252+ assert ( result . value . endsWith ( '-3' ) , `expected -N suffix, got ${ result . value } ` ) ;
42424253 // computeCompositeMPUChecksum's deterministic output for these
42434254 // exact placeholder digests:
42444255 const expected = crypto
42454256 . createHash ( 'sha256' )
42464257 . update ( Buffer . concat ( [ d1 , d2 , d3 ] . map ( x => Buffer . from ( x , 'base64' ) ) ) )
42474258 . digest ( 'base64' ) ;
4248- assert . strictEqual ( got . value , `${ expected } -3` ) ;
4259+ assert . strictEqual ( result . value , `${ expected } -3` ) ;
42494260 } ) ;
42504261
42514262 [ 'sha1' , 'crc32' , 'crc32c' ] . forEach ( algo => {
@@ -4255,18 +4266,19 @@ describe('computeFinalChecksum', () => {
42554266 makeStoredPart ( 1 , { algorithm : algo , value : d1 } ) ,
42564267 makeStoredPart ( 2 , { algorithm : algo , value : d2 } ) ,
42574268 ] ;
4258- const got = await computeFinalChecksum (
4269+ const { result , error } = await computeFinalChecksum (
42594270 stored ,
42604271 partListFromStored ( stored ) ,
42614272 { checksumAlgorithm : algo , checksumType : 'COMPOSITE' } ,
42624273 splitter ,
42634274 uploadId ,
42644275 log ,
42654276 ) ;
4266- assert ( got ) ;
4267- assert . strictEqual ( got . algorithm , algo ) ;
4268- assert . strictEqual ( got . type , 'COMPOSITE' ) ;
4269- assert ( got . value . endsWith ( '-2' ) ) ;
4277+ assert . strictEqual ( error , null ) ;
4278+ assert ( result ) ;
4279+ assert . strictEqual ( result . algorithm , algo ) ;
4280+ assert . strictEqual ( result . type , 'COMPOSITE' ) ;
4281+ assert ( result . value . endsWith ( '-2' ) ) ;
42704282 } ) ;
42714283 } ) ;
42724284
@@ -4299,23 +4311,47 @@ describe('computeFinalChecksum', () => {
42994311 } ,
43004312 } ,
43014313 ] ;
4302- const got = await computeFinalChecksum (
4314+ const { result , error } = await computeFinalChecksum (
43034315 stored ,
43044316 partListFromStored ( stored ) ,
43054317 { checksumAlgorithm : 'crc64nvme' , checksumType : 'FULL_OBJECT' } ,
43064318 splitter ,
43074319 uploadId ,
43084320 log ,
43094321 ) ;
4310- assert ( got ) ;
4311- assert . strictEqual ( got . algorithm , 'crc64nvme' ) ;
4312- assert . strictEqual ( got . type , 'FULL_OBJECT' ) ;
4313- assert ( ! got . value . includes ( '-' ) , `FULL_OBJECT should have no -N suffix, got ${ got . value } ` ) ;
4322+ assert . strictEqual ( error , null ) ;
4323+ assert ( result ) ;
4324+ assert . strictEqual ( result . algorithm , 'crc64nvme' ) ;
4325+ assert . strictEqual ( result . type , 'FULL_OBJECT' ) ;
4326+ assert ( ! result . value . includes ( '-' ) , `FULL_OBJECT should have no -N suffix, got ${ result . value } ` ) ;
43144327 const expected = await algorithms . crc64nvme . digest ( Buffer . concat ( [ a , b ] ) ) ;
4315- assert . strictEqual ( got . value , expected ) ;
4328+ assert . strictEqual ( result . value , expected ) ;
4329+ } ) ;
4330+
4331+ // Soft-null (`{ result: null, error: null }`) is intentional only for
4332+ // default MPUs — the client didn't opt in to a checksum, so missing it
4333+ // on the response is graceful degradation. Explicit MPUs return
4334+ // `{ result: null, error: InternalError }` because silently dropping
4335+ // a checksum the client asked for would violate the CreateMPU contract.
4336+
4337+ it ( 'should soft-null when a default-MPU part is missing ChecksumValue' , async ( ) => {
4338+ const stored = [
4339+ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ,
4340+ makeStoredPart ( 2 , null ) ,
4341+ makeStoredPart ( 3 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 1 ] } ) ,
4342+ ] ;
4343+ const got = await computeFinalChecksum (
4344+ stored ,
4345+ partListFromStored ( stored ) ,
4346+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'FULL_OBJECT' , checksumIsDefault : true } ,
4347+ splitter ,
4348+ uploadId ,
4349+ log ,
4350+ ) ;
4351+ assertSoftNull ( got ) ;
43164352 } ) ;
43174353
4318- it ( 'should return null and log when a part is missing ChecksumValue' , async ( ) => {
4354+ it ( 'should return InternalError when an explicit-MPU part is missing ChecksumValue' , async ( ) => {
43194355 const stored = [
43204356 makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ,
43214357 makeStoredPart ( 2 , null ) ,
@@ -4324,42 +4360,55 @@ describe('computeFinalChecksum', () => {
43244360 const got = await computeFinalChecksum (
43254361 stored ,
43264362 partListFromStored ( stored ) ,
4327- { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
4363+ { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' , checksumIsDefault : false } ,
4364+ splitter ,
4365+ uploadId ,
4366+ log ,
4367+ ) ;
4368+ assertInternalError ( got ) ;
4369+ } ) ;
4370+
4371+ it ( 'should soft-null when checksumType is unknown on a default MPU' , async ( ) => {
4372+ const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
4373+ const got = await computeFinalChecksum (
4374+ stored ,
4375+ partListFromStored ( stored ) ,
4376+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'WEIRD' , checksumIsDefault : true } ,
43284377 splitter ,
43294378 uploadId ,
43304379 log ,
43314380 ) ;
4332- assert . strictEqual ( got , null ) ;
4381+ assertSoftNull ( got ) ;
43334382 } ) ;
43344383
4335- it ( 'should return null when checksumType is unknown' , async ( ) => {
4384+ it ( 'should return InternalError when checksumType is unknown on an explicit MPU ' , async ( ) => {
43364385 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
43374386 const got = await computeFinalChecksum (
43384387 stored ,
43394388 partListFromStored ( stored ) ,
4340- { checksumAlgorithm : 'sha256' , checksumType : 'WEIRD' } ,
4389+ { checksumAlgorithm : 'sha256' , checksumType : 'WEIRD' , checksumIsDefault : false } ,
43414390 splitter ,
43424391 uploadId ,
43434392 log ,
43444393 ) ;
4345- assert . strictEqual ( got , null ) ;
4394+ assertInternalError ( got ) ;
43464395 } ) ;
43474396
4348- it (
4349- 'should return null when underlying compute reports an error ' + '(crc64nvme COMPOSITE is not allowed)' ,
4350- async ( ) => {
4351- const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
4352- const got = await computeFinalChecksum (
4353- stored ,
4354- partListFromStored ( stored ) ,
4355- { checksumAlgorithm : 'crc64nvme' , checksumType : 'COMPOSITE' } ,
4356- splitter ,
4357- uploadId ,
4358- log ,
4359- ) ;
4360- assert . strictEqual ( got , null ) ;
4361- } ,
4362- ) ;
4397+ it ( 'should return InternalError when underlying compute reports an error on an explicit MPU' , async ( ) => {
4398+ // crc64nvme + COMPOSITE is not allowed by computeCompositeMPUChecksum.
4399+ // Reaching here on an explicit MPU means upstream validation failed,
4400+ // which is exactly the kind of internal-state bug we want to surface.
4401+ const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
4402+ const got = await computeFinalChecksum (
4403+ stored ,
4404+ partListFromStored ( stored ) ,
4405+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'COMPOSITE' , checksumIsDefault : false } ,
4406+ splitter ,
4407+ uploadId ,
4408+ log ,
4409+ ) ;
4410+ assertInternalError ( got ) ;
4411+ } ) ;
43634412
43644413 it ( 'should compute over filteredPartList (subset), not all storedParts' , async ( ) => {
43654414 const [ d1 , d2 , d3 ] = [ SAMPLE_DIGESTS . sha256 [ 0 ] , SAMPLE_DIGESTS . sha256 [ 1 ] , SAMPLE_DIGESTS . sha256 [ 0 ] ] ;
@@ -4375,21 +4424,22 @@ describe('computeFinalChecksum', () => {
43754424 size : s . value . Size ,
43764425 locations : s . value . partLocations ,
43774426 } ) ) ;
4378- const got = await computeFinalChecksum (
4427+ const { result , error } = await computeFinalChecksum (
43794428 stored ,
43804429 filtered ,
43814430 { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
43824431 splitter ,
43834432 uploadId ,
43844433 log ,
43854434 ) ;
4386- assert ( got ) ;
4387- assert ( got . value . endsWith ( '-2' ) , `should reflect 2 completed parts, got ${ got . value } ` ) ;
4435+ assert . strictEqual ( error , null ) ;
4436+ assert ( result ) ;
4437+ assert ( result . value . endsWith ( '-2' ) , `should reflect 2 completed parts, got ${ result . value } ` ) ;
43884438 const expected = crypto
43894439 . createHash ( 'sha256' )
43904440 . update ( Buffer . concat ( [ d1 , d3 ] . map ( x => Buffer . from ( x , 'base64' ) ) ) )
43914441 . digest ( 'base64' ) ;
4392- assert . strictEqual ( got . value , `${ expected } -2` ) ;
4442+ assert . strictEqual ( result . value , `${ expected } -2` ) ;
43934443 } ) ;
43944444} ) ;
43954445
0 commit comments