diff --git a/backend/mp-lister.go b/backend/mp-lister.go index b4c444c4..b372ea23 100644 --- a/backend/mp-lister.go +++ b/backend/mp-lister.go @@ -113,7 +113,8 @@ func (l *MultipartUploadLister) Run() (*ListMultipartUploadsPage, error) { return emitted == l.MaxUploads } - for i, up := range l.Uploads[startIndex:] { + for i := startIndex; i < len(l.Uploads); i++ { + up := l.Uploads[i] if l.Delimiter != "" { // delimiter check suffix := strings.TrimPrefix(up.Key, l.Prefix) diff --git a/tests/integration/ListMultipartUploads.go b/tests/integration/ListMultipartUploads.go index 91605f3f..18aa1592 100644 --- a/tests/integration/ListMultipartUploads.go +++ b/tests/integration/ListMultipartUploads.go @@ -537,6 +537,60 @@ func ListMultipartUploads_delimiter_truncated(s *S3Conf) error { }) } +func ListMultipartUploads_upload_id_marker_truncated(s *S3Conf) error { + testName := "ListMultipartUploads_upload_id_marker_truncated" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := make([]types.MultipartUpload, 0, 3) + for _, key := range []string{"bar", "baz", "foo"} { + out, err := createMp(s3client, bucket, key) + if err != nil { + return err + } + uploads = append(uploads, types.MultipartUpload{ + Key: out.Key, + UploadId: out.UploadId, + StorageClass: types.StorageClassStandard, + }) + } + + // the listing starts after the first upload, so a page of 1 + // leaves one upload behind and a page of 2 reaches the end + for _, test := range []struct { + maxUploads int32 + expected []types.MultipartUpload + truncated bool + }{ + {1, uploads[1:2], true}, + {2, uploads[1:], false}, + } { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + KeyMarker: uploads[0].Key, + UploadIdMarker: uploads[0].UploadId, + MaxUploads: &test.maxUploads, + }) + cancel() + if err != nil { + return err + } + + if !compareMultipartUploads(test.expected, out.Uploads) { + return fmt.Errorf("expected the mp list to be %v, instead got %v", test.expected, out.Uploads) + } + if out.IsTruncated == nil { + return fmt.Errorf("unexpected nil is-truncated") + } + if *out.IsTruncated != test.truncated { + return fmt.Errorf("expected is-truncated to be %v with max-uploads %v, instead got %v", + test.truncated, test.maxUploads, *out.IsTruncated) + } + } + + return nil + }) +} + func ListMultipartUploads_prefix(s *S3Conf) error { testName := "ListMultipartUploads_prefix" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index 53699403..91d6cb79 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -558,6 +558,7 @@ func TestListMultipartUploads(ts *TestState) { ts.Run(ListMultipartUploads_keyMarker_not_from_list) ts.Run(ListMultipartUploads_upload_id_marker_with_key_marker) ts.Run(ListMultipartUploads_delimiter_truncated) + ts.Run(ListMultipartUploads_upload_id_marker_truncated) ts.Run(ListMultipartUploads_prefix) ts.Run(ListMultipartUploads_both_delimiter_and_prefix) ts.Run(ListMultipartUploads_delimiter_no_matches) @@ -3331,6 +3332,7 @@ func GetIntTests() IntTests { "ListMultipartUploads_keyMarker_not_from_list": ListMultipartUploads_keyMarker_not_from_list, "ListMultipartUploads_upload_id_marker_with_key_marker": ListMultipartUploads_upload_id_marker_with_key_marker, "ListMultipartUploads_delimiter_truncated": ListMultipartUploads_delimiter_truncated, + "ListMultipartUploads_upload_id_marker_truncated": ListMultipartUploads_upload_id_marker_truncated, "ListMultipartUploads_prefix": ListMultipartUploads_prefix, "ListMultipartUploads_both_delimiter_and_prefix": ListMultipartUploads_both_delimiter_and_prefix, "ListMultipartUploads_with_upload_id": ListMultipartUploads_with_upload_id,