Preserve asset inspection error causes through delivery

This commit is contained in:
2026-11-01 19:33:44 +00:00
parent eb61b40f08
commit e42cad7b1a
4 changed files with 127 additions and 5 deletions

View File

@@ -124,6 +124,7 @@ func uploadMissingAssets(ctx context.Context, backend forge.Backend, releaseHand
result = UploadResult{State: Partial}
preflightConflict := false
preflightUnverifiable := false
var inspectionErrors []error
var missing []artifact.ExpectedAsset
pending := make(map[string]struct{}, len(expected))
// Keep the initial observation order and compact pending identities once,
@@ -152,17 +153,20 @@ func uploadMissingAssets(ctx context.Context, backend forge.Backend, releaseHand
case AssetUnverifiable:
preflightUnverifiable = true
result.Uncertain = append(result.Uncertain, item)
if observation.Err != nil {
inspectionErrors = append(inspectionErrors, fmt.Errorf("verify existing attachment %q: %w", item.Filename, observation.Err))
}
default:
return result, &forge.Failure{Kind: forge.FailureInvalid, Err: fmt.Errorf("asset %q has no inspection result", observation.Expected.Filename)}
}
}
if preflightConflict {
result.State = Conflict
return result, &forge.Failure{Kind: forge.FailureConflict, Err: errors.New("one or more existing attachments conflict")}
return result, &forge.Failure{Kind: forge.FailureConflict, Err: errors.Join(errors.New("one or more existing attachments conflict"), errors.Join(inspectionErrors...))}
}
if preflightUnverifiable {
result.State = NotAttempted
return result, &forge.Failure{Kind: forge.FailureUnverifiable, Err: errors.New("one or more existing attachments could not be verified")}
return result, &forge.Failure{Kind: forge.FailureUnverifiable, Err: errors.Join(errors.New("one or more existing attachments could not be verified"), errors.Join(inspectionErrors...))}
}
if len(pending) == 0 {
result.State = Complete

View File

@@ -7,6 +7,7 @@ import (
"io"
"os"
"reflect"
"strings"
"testing"
"go.maximumdirect.net/releasekit/internal/artifact"
@@ -173,6 +174,35 @@ func TestUploadMissingAssetsStopsBeforeWritesForConflictOrUnverifiableContent(t
}
}
func TestUploadMissingAssetsPreservesInspectionCausesAndConflictPrecedence(t *testing.T) {
readErr := errors.New("asset download failed")
for _, conflict := range []bool{false, true} {
name := "unverifiable"
if conflict {
name = "conflict"
}
t.Run(name, func(t *testing.T) {
backend := &assetBackend{}
inspection := Inspection{Assets: []AssetInspection{
{Expected: expectedAsset("canceled.bin", []byte("a")), State: AssetUnverifiable, Err: context.Canceled},
{Expected: expectedAsset("unreadable.bin", []byte("b")), State: AssetUnverifiable, Err: readErr},
}}
wantState, wantKind := NotAttempted, forge.FailureUnverifiable
if conflict {
inspection.Assets = append(inspection.Assets, AssetInspection{Expected: expectedAsset("conflicting.bin", []byte("c")), State: AssetConflicting})
wantState, wantKind = Conflict, forge.FailureConflict
}
result, err := uploadMissingAssets(context.Background(), backend, "release", inspection)
if !isForgeFailure(err, wantKind) || result.State != wantState || len(result.Uncertain) != 2 || len(backend.uploadNames) != 0 {
t.Fatalf("result = %#v, error %v", result, err)
}
if !errors.Is(err, context.Canceled) || !errors.Is(err, readErr) || !strings.Contains(err.Error(), "canceled.bin") || !strings.Contains(err.Error(), "unreadable.bin") {
t.Fatalf("aggregate lost cause or asset context: %v", err)
}
})
}
}
func TestUploadMissingAssetsReconcilesOnceAndKeepsLaterAssetsPending(t *testing.T) {
assets := []artifact.ExpectedAsset{
expectedAsset("a.bin", []byte("a")),