@@ -573,13 +573,23 @@ describe('computeFinalChecksum', () => {
573573 } ) ) ;
574574 }
575575
576- it ( 'should return null when MPU has no checksumAlgorithm' , async ( ) => {
576+ function assertSoftNull ( got ) {
577+ assert . deepStrictEqual ( got , { result : null , error : null } ) ;
578+ }
579+
580+ function assertInternalError ( got ) {
581+ assert . strictEqual ( got . result , null ) ;
582+ assert ( got . error , 'expected an error on the result' ) ;
583+ assert . strictEqual ( got . error . is . InternalError , true ) ;
584+ }
585+
586+ it ( 'should return { result: null, error: null } when MPU has no checksumAlgorithm' , async ( ) => {
577587 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
578588 const got = await computeFinalChecksum ( stored , partListFromStored ( stored ) , { } , SPLITTER , uploadId , log ) ;
579- assert . strictEqual ( got , null ) ;
589+ assertSoftNull ( got ) ;
580590 } ) ;
581591
582- it ( 'should return null when MPU has no checksumType' , async ( ) => {
592+ it ( 'should return { result: null, error: null } when MPU has no checksumType' , async ( ) => {
583593 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
584594 const got = await computeFinalChecksum (
585595 stored ,
@@ -589,7 +599,7 @@ describe('computeFinalChecksum', () => {
589599 uploadId ,
590600 log ,
591601 ) ;
592- assert . strictEqual ( got , null ) ;
602+ assertSoftNull ( got ) ;
593603 } ) ;
594604
595605 it ( 'should return COMPOSITE checksum with -N suffix for SHA256 MPU' , async ( ) => {
@@ -599,25 +609,26 @@ describe('computeFinalChecksum', () => {
599609 makeStoredPart ( 2 , { algorithm : 'sha256' , value : d2 } ) ,
600610 makeStoredPart ( 3 , { algorithm : 'sha256' , value : d3 } ) ,
601611 ] ;
602- const got = await computeFinalChecksum (
612+ const { result , error } = await computeFinalChecksum (
603613 stored ,
604614 partListFromStored ( stored ) ,
605615 { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
606616 SPLITTER ,
607617 uploadId ,
608618 log ,
609619 ) ;
610- assert ( got ) ;
611- assert . strictEqual ( got . algorithm , 'sha256' ) ;
612- assert . strictEqual ( got . type , 'COMPOSITE' ) ;
613- assert ( got . value . endsWith ( '-3' ) , `expected -N suffix, got ${ got . value } ` ) ;
620+ assert . strictEqual ( error , null ) ;
621+ assert ( result ) ;
622+ assert . strictEqual ( result . algorithm , 'sha256' ) ;
623+ assert . strictEqual ( result . type , 'COMPOSITE' ) ;
624+ assert ( result . value . endsWith ( '-3' ) , `expected -N suffix, got ${ result . value } ` ) ;
614625 // computeCompositeMPUChecksum's deterministic output for these
615626 // exact placeholder digests:
616627 const expected = crypto
617628 . createHash ( 'sha256' )
618629 . update ( Buffer . concat ( [ d1 , d2 , d3 ] . map ( x => Buffer . from ( x , 'base64' ) ) ) )
619630 . digest ( 'base64' ) ;
620- assert . strictEqual ( got . value , `${ expected } -3` ) ;
631+ assert . strictEqual ( result . value , `${ expected } -3` ) ;
621632 } ) ;
622633
623634 [ 'sha1' , 'crc32' , 'crc32c' ] . forEach ( algo => {
@@ -627,18 +638,19 @@ describe('computeFinalChecksum', () => {
627638 makeStoredPart ( 1 , { algorithm : algo , value : d1 } ) ,
628639 makeStoredPart ( 2 , { algorithm : algo , value : d2 } ) ,
629640 ] ;
630- const got = await computeFinalChecksum (
641+ const { result , error } = await computeFinalChecksum (
631642 stored ,
632643 partListFromStored ( stored ) ,
633644 { checksumAlgorithm : algo , checksumType : 'COMPOSITE' } ,
634645 SPLITTER ,
635646 uploadId ,
636647 log ,
637648 ) ;
638- assert ( got ) ;
639- assert . strictEqual ( got . algorithm , algo ) ;
640- assert . strictEqual ( got . type , 'COMPOSITE' ) ;
641- assert ( got . value . endsWith ( '-2' ) ) ;
649+ assert . strictEqual ( error , null ) ;
650+ assert ( result ) ;
651+ assert . strictEqual ( result . algorithm , algo ) ;
652+ assert . strictEqual ( result . type , 'COMPOSITE' ) ;
653+ assert ( result . value . endsWith ( '-2' ) ) ;
642654 } ) ;
643655 } ) ;
644656
@@ -671,23 +683,47 @@ describe('computeFinalChecksum', () => {
671683 } ,
672684 } ,
673685 ] ;
674- const got = await computeFinalChecksum (
686+ const { result , error } = await computeFinalChecksum (
675687 stored ,
676688 partListFromStored ( stored ) ,
677689 { checksumAlgorithm : 'crc64nvme' , checksumType : 'FULL_OBJECT' } ,
678690 SPLITTER ,
679691 uploadId ,
680692 log ,
681693 ) ;
682- assert ( got ) ;
683- assert . strictEqual ( got . algorithm , 'crc64nvme' ) ;
684- assert . strictEqual ( got . type , 'FULL_OBJECT' ) ;
685- assert ( ! got . value . includes ( '-' ) , `FULL_OBJECT should have no -N suffix, got ${ got . value } ` ) ;
694+ assert . strictEqual ( error , null ) ;
695+ assert ( result ) ;
696+ assert . strictEqual ( result . algorithm , 'crc64nvme' ) ;
697+ assert . strictEqual ( result . type , 'FULL_OBJECT' ) ;
698+ assert ( ! result . value . includes ( '-' ) , `FULL_OBJECT should have no -N suffix, got ${ result . value } ` ) ;
686699 const expected = await algorithms . crc64nvme . digest ( Buffer . concat ( [ a , b ] ) ) ;
687- assert . strictEqual ( got . value , expected ) ;
700+ assert . strictEqual ( result . value , expected ) ;
701+ } ) ;
702+
703+ // Soft-null (`{ result: null, error: null }`) is intentional only for
704+ // default MPUs — the client didn't opt in to a checksum, so missing it
705+ // on the response is graceful degradation. Explicit MPUs return
706+ // `{ result: null, error: InternalError }` because silently dropping
707+ // a checksum the client asked for would violate the CreateMPU contract.
708+
709+ it ( 'should soft-null when a default-MPU part is missing ChecksumValue' , async ( ) => {
710+ const stored = [
711+ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ,
712+ makeStoredPart ( 2 , null ) ,
713+ makeStoredPart ( 3 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 1 ] } ) ,
714+ ] ;
715+ const got = await computeFinalChecksum (
716+ stored ,
717+ partListFromStored ( stored ) ,
718+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'FULL_OBJECT' , checksumIsDefault : true } ,
719+ SPLITTER ,
720+ uploadId ,
721+ log ,
722+ ) ;
723+ assertSoftNull ( got ) ;
688724 } ) ;
689725
690- it ( 'should return null and log when a part is missing ChecksumValue' , async ( ) => {
726+ it ( 'should return InternalError when an explicit-MPU part is missing ChecksumValue' , async ( ) => {
691727 const stored = [
692728 makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ,
693729 makeStoredPart ( 2 , null ) ,
@@ -696,42 +732,55 @@ describe('computeFinalChecksum', () => {
696732 const got = await computeFinalChecksum (
697733 stored ,
698734 partListFromStored ( stored ) ,
699- { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
735+ { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' , checksumIsDefault : false } ,
736+ SPLITTER ,
737+ uploadId ,
738+ log ,
739+ ) ;
740+ assertInternalError ( got ) ;
741+ } ) ;
742+
743+ it ( 'should soft-null when checksumType is unknown on a default MPU' , async ( ) => {
744+ const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
745+ const got = await computeFinalChecksum (
746+ stored ,
747+ partListFromStored ( stored ) ,
748+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'WEIRD' , checksumIsDefault : true } ,
700749 SPLITTER ,
701750 uploadId ,
702751 log ,
703752 ) ;
704- assert . strictEqual ( got , null ) ;
753+ assertSoftNull ( got ) ;
705754 } ) ;
706755
707- it ( 'should return null when checksumType is unknown' , async ( ) => {
756+ it ( 'should return InternalError when checksumType is unknown on an explicit MPU ' , async ( ) => {
708757 const stored = [ makeStoredPart ( 1 , { algorithm : 'sha256' , value : SAMPLE_DIGESTS . sha256 [ 0 ] } ) ] ;
709758 const got = await computeFinalChecksum (
710759 stored ,
711760 partListFromStored ( stored ) ,
712- { checksumAlgorithm : 'sha256' , checksumType : 'WEIRD' } ,
761+ { checksumAlgorithm : 'sha256' , checksumType : 'WEIRD' , checksumIsDefault : false } ,
713762 SPLITTER ,
714763 uploadId ,
715764 log ,
716765 ) ;
717- assert . strictEqual ( got , null ) ;
766+ assertInternalError ( got ) ;
718767 } ) ;
719768
720- it (
721- 'should return null when underlying compute reports an error ' + '(crc64nvme COMPOSITE is not allowed)' ,
722- async ( ) => {
723- const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
724- const got = await computeFinalChecksum (
725- stored ,
726- partListFromStored ( stored ) ,
727- { checksumAlgorithm : 'crc64nvme' , checksumType : 'COMPOSITE' } ,
728- SPLITTER ,
729- uploadId ,
730- log ,
731- ) ;
732- assert . strictEqual ( got , null ) ;
733- } ,
734- ) ;
769+ it ( 'should return InternalError when underlying compute reports an error on an explicit MPU' , async ( ) => {
770+ // crc64nvme + COMPOSITE is not allowed by computeCompositeMPUChecksum.
771+ // Reaching here on an explicit MPU means upstream validation failed,
772+ // which is exactly the kind of internal-state bug we want to surface.
773+ const stored = [ makeStoredPart ( 1 , { algorithm : 'crc64nvme' , value : SAMPLE_DIGESTS . crc64nvme [ 0 ] } ) ] ;
774+ const got = await computeFinalChecksum (
775+ stored ,
776+ partListFromStored ( stored ) ,
777+ { checksumAlgorithm : 'crc64nvme' , checksumType : 'COMPOSITE' , checksumIsDefault : false } ,
778+ SPLITTER ,
779+ uploadId ,
780+ log ,
781+ ) ;
782+ assertInternalError ( got ) ;
783+ } ) ;
735784
736785 it ( 'should compute over filteredPartList (subset), not all storedParts' , async ( ) => {
737786 const [ d1 , d2 , d3 ] = [ SAMPLE_DIGESTS . sha256 [ 0 ] , SAMPLE_DIGESTS . sha256 [ 1 ] , SAMPLE_DIGESTS . sha256 [ 0 ] ] ;
@@ -747,21 +796,22 @@ describe('computeFinalChecksum', () => {
747796 size : s . value . Size ,
748797 locations : s . value . partLocations ,
749798 } ) ) ;
750- const got = await computeFinalChecksum (
799+ const { result , error } = await computeFinalChecksum (
751800 stored ,
752801 filtered ,
753802 { checksumAlgorithm : 'sha256' , checksumType : 'COMPOSITE' } ,
754803 SPLITTER ,
755804 uploadId ,
756805 log ,
757806 ) ;
758- assert ( got ) ;
759- assert ( got . value . endsWith ( '-2' ) , `should reflect 2 completed parts, got ${ got . value } ` ) ;
807+ assert . strictEqual ( error , null ) ;
808+ assert ( result ) ;
809+ assert ( result . value . endsWith ( '-2' ) , `should reflect 2 completed parts, got ${ result . value } ` ) ;
760810 const expected = crypto
761811 . createHash ( 'sha256' )
762812 . update ( Buffer . concat ( [ d1 , d3 ] . map ( x => Buffer . from ( x , 'base64' ) ) ) )
763813 . digest ( 'base64' ) ;
764- assert . strictEqual ( got . value , `${ expected } -2` ) ;
814+ assert . strictEqual ( result . value , `${ expected } -2` ) ;
765815 } ) ;
766816} ) ;
767817
0 commit comments