@@ -574,13 +574,23 @@ describe('computeFinalChecksum', () => {
574574 } ) ) ;
575575 }
576576
577- it ( 'should return null when MPU has no checksumAlgorithm' , async ( ) => {
577+ function assertSoftNull ( got ) {
578+ assert . deepStrictEqual ( got , { result : null , error : null } ) ;
579+ }
580+
581+ function assertInternalError ( got ) {
582+ assert . strictEqual ( got . result , null ) ;
583+ assert ( got . error , 'expected an error on the result' ) ;
584+ assert . strictEqual ( got . error . is . InternalError , true ) ;
585+ }
586+
587+ it ( 'should return { result: null, error: null } when MPU has no checksumAlgorithm' , async ( ) => {
578588 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
579589 const got = await computeFinalChecksum ( stored , partListFromStored ( stored ) , { } , SPLITTER , uploadId , log ) ;
580- assert . strictEqual ( got , null ) ;
590+ assertSoftNull ( got ) ;
581591 } ) ;
582592
583- it ( 'should return null when MPU has no checksumType' , async ( ) => {
593+ it ( 'should return { result: null, error: null } when MPU has no checksumType' , async ( ) => {
584594 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
585595 const got = await computeFinalChecksum (
586596 stored ,
@@ -590,7 +600,7 @@ describe('computeFinalChecksum', () => {
590600 uploadId ,
591601 log ,
592602 ) ;
593- assert . strictEqual ( got , null ) ;
603+ assertSoftNull ( got ) ;
594604 } ) ;
595605
596606 it ( 'should return COMPOSITE checksum with -N suffix for SHA256 MPU' , async ( ) => {
@@ -600,25 +610,26 @@ describe('computeFinalChecksum', () => {
600610 makeStoredPart ( 2 , { algorithm : 'sha256' , value : d2 } ) ,
601611 makeStoredPart ( 3 , { algorithm : 'sha256' , value : d3 } ) ,
602612 ] ;
603- const got = await computeFinalChecksum (
613+ const { result , error } = await computeFinalChecksum (
604614 stored ,
605615 partListFromStored ( stored ) ,
606616 { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
607617 SPLITTER ,
608618 uploadId ,
609619 log ,
610620 ) ;
611- assert ( got ) ;
612- assert . strictEqual ( got . algorithm , 'sha256' ) ;
613- assert . strictEqual ( got . type , 'COMPOSITE' ) ;
614- assert ( got . value . endsWith ( '-3' ) , `expected -N suffix, got ${ got . value } ` ) ;
621+ assert . strictEqual ( error , null ) ;
622+ assert ( result ) ;
623+ assert . strictEqual ( result . algorithm , 'sha256' ) ;
624+ assert . strictEqual ( result . type , 'COMPOSITE' ) ;
625+ assert ( result . value . endsWith ( '-3' ) , `expected -N suffix, got ${ result . value } ` ) ;
615626 // computeCompositeMPUChecksum's deterministic output for these
616627 // exact placeholder digests:
617628 const expected = crypto
618629 . createHash ( 'sha256' )
619630 . update ( Buffer . concat ( [ d1 , d2 , d3 ] . map ( x => Buffer . from ( x , 'base64' ) ) ) )
620631 . digest ( 'base64' ) ;
621- assert . strictEqual ( got . value , `${ expected } -3` ) ;
632+ assert . strictEqual ( result . value , `${ expected } -3` ) ;
622633 } ) ;
623634
624635 [ 'sha1' , 'crc32' , 'crc32c' ] . forEach ( algo => {
@@ -628,18 +639,19 @@ describe('computeFinalChecksum', () => {
628639 makeStoredPart ( 1 , { algorithm : algo , value : d1 } ) ,
629640 makeStoredPart ( 2 , { algorithm : algo , value : d2 } ) ,
630641 ] ;
631- const got = await computeFinalChecksum (
642+ const { result , error } = await computeFinalChecksum (
632643 stored ,
633644 partListFromStored ( stored ) ,
634645 { checksumAlgorithm : algo , checksumType : 'COMPOSITE' } ,
635646 SPLITTER ,
636647 uploadId ,
637648 log ,
638649 ) ;
639- assert ( got ) ;
640- assert . strictEqual ( got . algorithm , algo ) ;
641- assert . strictEqual ( got . type , 'COMPOSITE' ) ;
642- assert ( got . value . endsWith ( '-2' ) ) ;
650+ assert . strictEqual ( error , null ) ;
651+ assert ( result ) ;
652+ assert . strictEqual ( result . algorithm , algo ) ;
653+ assert . strictEqual ( result . type , 'COMPOSITE' ) ;
654+ assert ( result . value . endsWith ( '-2' ) ) ;
643655 } ) ;
644656 } ) ;
645657
@@ -672,23 +684,47 @@ describe('computeFinalChecksum', () => {
672684 } ,
673685 } ,
674686 ] ;
675- const got = await computeFinalChecksum (
687+ const { result , error } = await computeFinalChecksum (
676688 stored ,
677689 partListFromStored ( stored ) ,
678690 { checksumAlgorithm : 'crc64nvme' , checksumType : 'FULL_OBJECT' } ,
679691 SPLITTER ,
680692 uploadId ,
681693 log ,
682694 ) ;
683- assert ( got ) ;
684- assert . strictEqual ( got . algorithm , 'crc64nvme' ) ;
685- assert . strictEqual ( got . type , 'FULL_OBJECT' ) ;
686- assert ( ! got . value . includes ( '-' ) , `FULL_OBJECT should have no -N suffix, got ${ got . value } ` ) ;
695+ assert . strictEqual ( error , null ) ;
696+ assert ( result ) ;
697+ assert . strictEqual ( result . algorithm , 'crc64nvme' ) ;
698+ assert . strictEqual ( result . type , 'FULL_OBJECT' ) ;
699+ assert ( ! result . value . includes ( '-' ) , `FULL_OBJECT should have no -N suffix, got ${ result . value } ` ) ;
687700 const expected = await algorithms . crc64nvme . digest ( Buffer . concat ( [ a , b ] ) ) ;
688- assert . strictEqual ( got . value , expected ) ;
701+ assert . strictEqual ( result . value , expected ) ;
702+ } ) ;
703+
704+ // Soft-null (`{ result: null, error: null }`) is intentional only for
705+ // default MPUs — the client didn't opt in to a checksum, so missing it
706+ // on the response is graceful degradation. Explicit MPUs return
707+ // `{ result: null, error: InternalError }` because silently dropping
708+ // a checksum the client asked for would violate the CreateMPU contract.
709+
710+ it ( 'should soft-null when a default-MPU part is missing ChecksumValue' , async ( ) => {
711+ const stored = [
712+ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ,
713+ makeStoredPart ( 2 , null ) ,
714+ makeStoredPart ( 3 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 1 ] } ) ,
715+ ] ;
716+ const got = await computeFinalChecksum (
717+ stored ,
718+ partListFromStored ( stored ) ,
719+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'FULL_OBJECT' , checksumIsDefault : true } ,
720+ SPLITTER ,
721+ uploadId ,
722+ log ,
723+ ) ;
724+ assertSoftNull ( got ) ;
689725 } ) ;
690726
691- it ( 'should return null and log when a part is missing ChecksumValue' , async ( ) => {
727+ it ( 'should return InternalError when an explicit-MPU part is missing ChecksumValue' , async ( ) => {
692728 const stored = [
693729 makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ,
694730 makeStoredPart ( 2 , null ) ,
@@ -697,42 +733,55 @@ describe('computeFinalChecksum', () => {
697733 const got = await computeFinalChecksum (
698734 stored ,
699735 partListFromStored ( stored ) ,
700- { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
736+ { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' , checksumIsDefault : false } ,
737+ SPLITTER ,
738+ uploadId ,
739+ log ,
740+ ) ;
741+ assertInternalError ( got ) ;
742+ } ) ;
743+
744+ it ( 'should soft-null when checksumType is unknown on a default MPU' , async ( ) => {
745+ const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
746+ const got = await computeFinalChecksum (
747+ stored ,
748+ partListFromStored ( stored ) ,
749+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'WEIRD' , checksumIsDefault : true } ,
701750 SPLITTER ,
702751 uploadId ,
703752 log ,
704753 ) ;
705- assert . strictEqual ( got , null ) ;
754+ assertSoftNull ( got ) ;
706755 } ) ;
707756
708- it ( 'should return null when checksumType is unknown' , async ( ) => {
757+ it ( 'should return InternalError when checksumType is unknown on an explicit MPU ' , async ( ) => {
709758 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
710759 const got = await computeFinalChecksum (
711760 stored ,
712761 partListFromStored ( stored ) ,
713- { checksumAlgorithm : 'sha256' , checksumType : 'WEIRD' } ,
762+ { checksumAlgorithm : 'sha256' , checksumType : 'WEIRD' , checksumIsDefault : false } ,
714763 SPLITTER ,
715764 uploadId ,
716765 log ,
717766 ) ;
718- assert . strictEqual ( got , null ) ;
767+ assertInternalError ( got ) ;
719768 } ) ;
720769
721- it (
722- 'should return null when underlying compute reports an error ' + '(crc64nvme COMPOSITE is not allowed)' ,
723- async ( ) => {
724- const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
725- const got = await computeFinalChecksum (
726- stored ,
727- partListFromStored ( stored ) ,
728- { checksumAlgorithm : 'crc64nvme' , checksumType : 'COMPOSITE' } ,
729- SPLITTER ,
730- uploadId ,
731- log ,
732- ) ;
733- assert . strictEqual ( got , null ) ;
734- } ,
735- ) ;
770+ it ( 'should return InternalError when underlying compute reports an error on an explicit MPU' , async ( ) => {
771+ // crc64nvme + COMPOSITE is not allowed by computeCompositeMPUChecksum.
772+ // Reaching here on an explicit MPU means upstream validation failed,
773+ // which is exactly the kind of internal-state bug we want to surface.
774+ const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
775+ const got = await computeFinalChecksum (
776+ stored ,
777+ partListFromStored ( stored ) ,
778+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'COMPOSITE' , checksumIsDefault : false } ,
779+ SPLITTER ,
780+ uploadId ,
781+ log ,
782+ ) ;
783+ assertInternalError ( got ) ;
784+ } ) ;
736785
737786 it ( 'should compute over filteredPartList (subset), not all storedParts' , async ( ) => {
738787 const [ d1 , d2 , d3 ] = [ SAMPLE_DIGESTS . sha256 [ 0 ] , SAMPLE_DIGESTS . sha256 [ 1 ] , SAMPLE_DIGESTS . sha256 [ 0 ] ] ;
@@ -748,21 +797,22 @@ describe('computeFinalChecksum', () => {
748797 size : s . value . Size ,
749798 locations : s . value . partLocations ,
750799 } ) ) ;
751- const got = await computeFinalChecksum (
800+ const { result , error } = await computeFinalChecksum (
752801 stored ,
753802 filtered ,
754803 { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
755804 SPLITTER ,
756805 uploadId ,
757806 log ,
758807 ) ;
759- assert ( got ) ;
760- assert ( got . value . endsWith ( '-2' ) , `should reflect 2 completed parts, got ${ got . value } ` ) ;
808+ assert . strictEqual ( error , null ) ;
809+ assert ( result ) ;
810+ assert ( result . value . endsWith ( '-2' ) , `should reflect 2 completed parts, got ${ result . value } ` ) ;
761811 const expected = crypto
762812 . createHash ( 'sha256' )
763813 . update ( Buffer . concat ( [ d1 , d3 ] . map ( x => Buffer . from ( x , 'base64' ) ) ) )
764814 . digest ( 'base64' ) ;
765- assert . strictEqual ( got . value , `${ expected } -2` ) ;
815+ assert . strictEqual ( result . value , `${ expected } -2` ) ;
766816 } ) ;
767817} ) ;
768818
0 commit comments