@@ -382,7 +382,7 @@ func TestArtifactUploader_ConcurrentUploads(t *testing.T) {
382382}
383383
384384// TestArtifactUploader_SkipsExistingArtifacts tests that existing artifacts are not re-uploaded
385- // and attestation upload is also skipped to prevent overwriting existing attestations
385+ // and attestation upload is also skipped when both artifact and attestation exist
386386func TestArtifactUploader_SkipsExistingArtifacts (t * testing.T ) {
387387 tmpDir := t .TempDir ()
388388 artifactPath := filepath .Join (tmpDir , "existing.tar.gz" )
@@ -395,8 +395,10 @@ func TestArtifactUploader_SkipsExistingArtifacts(t *testing.T) {
395395 uploadCalls : make (map [string ]int ),
396396 }
397397
398- // Pre-populate the cache with the artifact (simulating it already exists)
398+ // Pre-populate the cache with both artifact and attestation (simulating they already exist)
399+ existingAttestation := []byte (`{"existing":"attestation"}` )
399400 mockCache .uploadedFiles ["existing.tar.gz" ] = artifactContent
401+ mockCache .uploadedFiles ["existing.tar.gz.att" ] = existingAttestation
400402
401403 uploader := NewArtifactUploader (mockCache )
402404 attestation := []byte (`{"test":"attestation"}` )
@@ -407,10 +409,43 @@ func TestArtifactUploader_SkipsExistingArtifacts(t *testing.T) {
407409 // CRITICAL: Verify that UploadFile was NOT called for the artifact
408410 assert .Equal (t , 0 , mockCache .uploadCalls ["existing.tar.gz" ], "Artifact should not be uploaded when it already exists" )
409411
410- // CRITICAL: Verify that the attestation was NOT uploaded (FIXED BEHAVIOR)
411- // This prevents overwriting the existing artifact's attestation with a potentially different one
412- assert .Equal (t , 0 , mockCache .uploadCalls ["existing.tar.gz.att" ], "Attestation should NOT be uploaded when artifact already exists" )
413- assert .NotContains (t , mockCache .uploadedFiles , "existing.tar.gz.att" , "Attestation should NOT be in cache" )
412+ // CRITICAL: Verify that the attestation was NOT uploaded when both exist
413+ assert .Equal (t , 0 , mockCache .uploadCalls ["existing.tar.gz.att" ], "Attestation should NOT be uploaded when both artifact and attestation exist" )
414+
415+ // Verify existing attestation is preserved
416+ assert .Equal (t , existingAttestation , mockCache .uploadedFiles ["existing.tar.gz.att" ], "Existing attestation should be preserved" )
417+ }
418+
419+ // TestArtifactUploader_UploadsAttestationWhenMissing tests that attestation is uploaded
420+ // when artifact exists but attestation is missing (e.g., old build before SLSA)
421+ func TestArtifactUploader_UploadsAttestationWhenMissing (t * testing.T ) {
422+ tmpDir := t .TempDir ()
423+ artifactPath := filepath .Join (tmpDir , "existing.tar.gz" )
424+ artifactContent := []byte ("existing artifact content" )
425+ err := os .WriteFile (artifactPath , artifactContent , 0644 )
426+ require .NoError (t , err )
427+
428+ mockCache := & mockRemoteCacheUpload {
429+ uploadedFiles : make (map [string ][]byte ),
430+ uploadCalls : make (map [string ]int ),
431+ }
432+
433+ // Pre-populate the cache with artifact only (no attestation - simulating old build)
434+ mockCache .uploadedFiles ["existing.tar.gz" ] = artifactContent
435+
436+ uploader := NewArtifactUploader (mockCache )
437+ attestation := []byte (`{"test":"attestation"}` )
438+
439+ err = uploader .UploadArtifactWithAttestation (context .Background (), artifactPath , attestation )
440+ require .NoError (t , err )
441+
442+ // Verify that artifact was NOT re-uploaded
443+ assert .Equal (t , 0 , mockCache .uploadCalls ["existing.tar.gz" ], "Artifact should not be re-uploaded when it already exists" )
444+
445+ // CRITICAL: Verify that attestation WAS uploaded (missing attestation case)
446+ assert .Equal (t , 1 , mockCache .uploadCalls ["existing.tar.gz.att" ], "Attestation should be uploaded when missing" )
447+ assert .Contains (t , mockCache .uploadedFiles , "existing.tar.gz.att" , "Attestation should be in cache" )
448+ assert .Equal (t , attestation , mockCache .uploadedFiles ["existing.tar.gz.att" ], "Attestation content should match" )
414449}
415450
416451// TestArtifactUploader_UploadsNewArtifacts tests that new artifacts are uploaded
@@ -459,10 +494,13 @@ func TestArtifactUploader_SimulatesDownloadedArtifactWorkflow(t *testing.T) {
459494 uploadCalls : make (map [string ]int ),
460495 }
461496
462- // Step 1: Simulate build job uploading artifact
497+ // Step 1: Simulate build job uploading artifact and attestation
463498 buildArtifactContent := []byte ("artifact built by build job" )
499+ buildAttestation := []byte (`{"build":"attestation"}` )
464500 mockCache .uploadedFiles ["downloaded.tar.gz" ] = buildArtifactContent
501+ mockCache .uploadedFiles ["downloaded.tar.gz.att" ] = buildAttestation
465502 mockCache .uploadCalls ["downloaded.tar.gz" ] = 1 // Track that build job uploaded it
503+ mockCache .uploadCalls ["downloaded.tar.gz.att" ] = 1
466504
467505 // Step 2: Simulate sign job downloading artifact (creates local file)
468506 downloadedArtifactPath := filepath .Join (tmpDir , "downloaded.tar.gz" )
@@ -481,12 +519,13 @@ func TestArtifactUploader_SimulatesDownloadedArtifactWorkflow(t *testing.T) {
481519 assert .Equal (t , 1 , mockCache .uploadCalls ["downloaded.tar.gz" ],
482520 "Downloaded artifact should NOT be re-uploaded by sign job" )
483521
484- // CRITICAL: Attestation should NOT be uploaded
485- // This prevents overwriting the existing artifact's attestation
486- assert .Equal (t , 0 , mockCache .uploadCalls ["downloaded.tar.gz.att" ],
487- "Attestation should NOT be uploaded when artifact already exists" )
488- assert .NotContains (t , mockCache .uploadedFiles , "downloaded.tar.gz.att" ,
489- "Attestation should NOT be in cache" )
522+ // CRITICAL: Attestation should NOT be uploaded when both exist
523+ assert .Equal (t , 1 , mockCache .uploadCalls ["downloaded.tar.gz.att" ],
524+ "Attestation should NOT be uploaded when both artifact and attestation exist" )
525+
526+ // Verify existing attestation is preserved
527+ assert .Equal (t , buildAttestation , mockCache .uploadedFiles ["downloaded.tar.gz.att" ],
528+ "Existing attestation should be preserved" )
490529
491530 // Artifact content should remain unchanged (not overwritten)
492531 assert .Equal (t , buildArtifactContent , mockCache .uploadedFiles ["downloaded.tar.gz" ],
@@ -632,3 +671,53 @@ func (m *mockRemoteCacheUploadWithHasFileError) HasFile(ctx context.Context, key
632671 }
633672 return m .mockRemoteCacheUpload .HasFile (ctx , key )
634673}
674+
675+ // TestArtifactUploader_AttestationCheckError tests fallback behavior when attestation HasFile check fails
676+ func TestArtifactUploader_AttestationCheckError (t * testing.T ) {
677+ tmpDir := t .TempDir ()
678+ artifactPath := filepath .Join (tmpDir , "test.tar.gz" )
679+ artifactContent := []byte ("test content" )
680+ err := os .WriteFile (artifactPath , artifactContent , 0644 )
681+ require .NoError (t , err )
682+
683+ // Create a mock where artifact exists but attestation check fails
684+ mockCache := & mockRemoteCacheUploadWithSelectiveError {
685+ mockRemoteCacheUpload : mockRemoteCacheUpload {
686+ uploadedFiles : map [string ][]byte {
687+ "test.tar.gz" : artifactContent , // Artifact exists
688+ },
689+ uploadCalls : make (map [string ]int ),
690+ },
691+ errorKeys : map [string ]error {
692+ "test.tar.gz.att" : fmt .Errorf ("S3 connection failed for attestation check" ),
693+ },
694+ }
695+
696+ uploader := NewArtifactUploader (mockCache )
697+ attestation := []byte (`{"test":"attestation"}` )
698+
699+ // Should proceed with attestation upload despite HasFile error (safe fallback)
700+ err = uploader .UploadArtifactWithAttestation (context .Background (), artifactPath , attestation )
701+ require .NoError (t , err )
702+
703+ // Artifact should not be re-uploaded
704+ assert .Equal (t , 0 , mockCache .uploadCalls ["test.tar.gz" ],
705+ "Artifact should not be uploaded when it exists" )
706+
707+ // Attestation should be uploaded (fallback behavior assumes it doesn't exist)
708+ assert .Equal (t , 1 , mockCache .uploadCalls ["test.tar.gz.att" ],
709+ "Attestation should be uploaded when HasFile check fails" )
710+ }
711+
712+ // mockRemoteCacheUploadWithSelectiveError allows different errors for different keys
713+ type mockRemoteCacheUploadWithSelectiveError struct {
714+ mockRemoteCacheUpload
715+ errorKeys map [string ]error
716+ }
717+
718+ func (m * mockRemoteCacheUploadWithSelectiveError ) HasFile (ctx context.Context , key string ) (bool , error ) {
719+ if err , exists := m .errorKeys [key ]; exists {
720+ return false , err
721+ }
722+ return m .mockRemoteCacheUpload .HasFile (ctx , key )
723+ }
0 commit comments