diff --git a/test/s3/lifecycle/s3_lifecycle_multi_bucket_test.go b/test/s3/lifecycle/s3_lifecycle_multi_bucket_test.go index d00ac33bf..8d885a928 100644 --- a/test/s3/lifecycle/s3_lifecycle_multi_bucket_test.go +++ b/test/s3/lifecycle/s3_lifecycle_multi_bucket_test.go @@ -3,14 +3,40 @@ package lifecycle import ( "context" + "errors" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/require" ) +// isS3NotFound recognizes a NotFound (NoSuchKey/404) response from the +// AWS SDK. Treating any HeadObject error as "deleted" lets a transport +// failure or dead endpoint mask a real bug, so callers that need to +// prove deletion specifically should use this. +func isS3NotFound(err error) bool { + if err == nil { + return false + } + var nsk *types.NoSuchKey + if errors.As(err, &nsk) { + return true + } + var notFound *types.NotFound + if errors.As(err, ¬Found) { + return true + } + var apiErr *smithyhttp.ResponseError + if errors.As(err, &apiErr) { + return apiErr.HTTPStatusCode() == 404 + } + return false +} + // TestLifecycleMultipleBucketsInOneSweep: a single shell-driven shard // sweep must process every bucket carrying lifecycle config, not just // the first one alphabetically. Pinned because the scheduler iterates @@ -54,7 +80,10 @@ func TestLifecycleMultipleBucketsInOneSweep(t *testing.T) { _, err := c.HeadObject(context.Background(), &s3.HeadObjectInput{ Bucket: aws.String(c2.bucket), Key: aws.String(c2.key), }) - return err != nil + // Pin: only count 404 / NoSuchKey as deletion. Any other + // error (transport failure, dead endpoint, auth) would + // otherwise mask a real bug as a "passed" test. + return isS3NotFound(err) }, 30*time.Second, 500*time.Millisecond, "%s/%s must be expired by the multi-bucket sweep", c2.bucket, c2.key) }