diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 70bbe4a..8ff09eb 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -1640,7 +1640,15 @@ func (p *Posix) ListObjectsV2(_ context.Context, input *s3.ListObjectsV2Input) ( } marker := "" if input.ContinuationToken != nil { - marker = *input.ContinuationToken + if input.StartAfter != nil { + if *input.StartAfter > *input.ContinuationToken { + marker = *input.StartAfter + } else { + marker = *input.ContinuationToken + } + } else { + marker = *input.ContinuationToken + } } delim := "" if input.Delimiter != nil { diff --git a/cmd/versitygw/test.go b/cmd/versitygw/test.go index 822612c..ac88cb3 100644 --- a/cmd/versitygw/test.go +++ b/cmd/versitygw/test.go @@ -268,11 +268,11 @@ func getAction(tf testFunc) func(*cli.Context) error { func extractIntTests() (commands []*cli.Command) { tests := integration.GetIntTests() for key, val := range tests { - testKey := key + k := key testFunc := val commands = append(commands, &cli.Command{ - Name: testKey, - Usage: fmt.Sprintf("Runs %v integration test", testKey), + Name: k, + Usage: fmt.Sprintf("Runs %v integration test", key), Action: func(ctx *cli.Context) error { opts := []integration.Option{ integration.WithAccess(awsID), diff --git a/integration/group-tests.go b/integration/group-tests.go index b63a904..d0421c0 100644 --- a/integration/group-tests.go +++ b/integration/group-tests.go @@ -97,6 +97,13 @@ func TestListObjects(s *S3Conf) { ListObjects_marker_not_from_obj_list(s) } +func TestListObjectsV2(s *S3Conf) { + ListObjectsV2_start_after(s) + ListObjectsV2_both_start_after_and_continuation_token(s) + ListObjectsV2_start_after_not_in_list(s) + ListObjectsV2_start_after_empty_result(s) +} + func TestDeleteObject(s *S3Conf) { DeleteObject_non_existing_object(s) DeleteObject_success(s) @@ -223,6 +230,7 @@ func TestFullFlow(s *S3Conf) { TestHeadObject(s) TestGetObject(s) TestListObjects(s) + TestListObjectsV2(s) TestDeleteObject(s) TestDeleteObjects(s) TestCopyObject(s) @@ -250,137 +258,141 @@ type IntTests map[string]func(s *S3Conf) error func GetIntTests() IntTests { return IntTests{ - "Authentication_empty_auth_header": Authentication_empty_auth_header, - "Authentication_invalid_auth_header": Authentication_invalid_auth_header, - "Authentication_unsupported_signature_version": Authentication_unsupported_signature_version, - "Authentication_malformed_credentials": Authentication_malformed_credentials, - "Authentication_malformed_credentials_invalid_parts": Authentication_malformed_credentials_invalid_parts, - "Authentication_credentials_terminated_string": Authentication_credentials_terminated_string, - "Authentication_credentials_incorrect_service": Authentication_credentials_incorrect_service, - "Authentication_credentials_incorrect_region": Authentication_credentials_incorrect_region, - "Authentication_credentials_invalid_date": Authentication_credentials_invalid_date, - "Authentication_credentials_future_date": Authentication_credentials_future_date, - "Authentication_credentials_past_date": Authentication_credentials_past_date, - "Authentication_credentials_non_existing_access_key": Authentication_credentials_non_existing_access_key, - "Authentication_invalid_signed_headers": Authentication_invalid_signed_headers, - "Authentication_missing_date_header": Authentication_missing_date_header, - "Authentication_invalid_date_header": Authentication_invalid_date_header, - "Authentication_date_mismatch": Authentication_date_mismatch, - "Authentication_incorrect_payload_hash": Authentication_incorrect_payload_hash, - "Authentication_incorrect_md5": Authentication_incorrect_md5, - "Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key, - "CreateBucket_invalid_bucket_name": CreateBucket_invalid_bucket_name, - "CreateBucket_existing_bucket": CreateBucket_existing_bucket, - "CreateBucket_as_user": CreateBucket_as_user, - "CreateDeleteBucket_success": CreateDeleteBucket_success, - "CreateBucket_default_acl": CreateBucket_default_acl, - "CreateBucket_non_default_acl": CreateBucket_non_default_acl, - "HeadBucket_non_existing_bucket": HeadBucket_non_existing_bucket, - "HeadBucket_success": HeadBucket_success, - "ListBuckets_as_user": ListBuckets_as_user, - "ListBuckets_as_admin": ListBuckets_as_admin, - "ListBuckets_success": ListBuckets_success, - "DeleteBucket_non_existing_bucket": DeleteBucket_non_existing_bucket, - "DeleteBucket_non_empty_bucket": DeleteBucket_non_empty_bucket, - "DeleteBucket_success_status_code": DeleteBucket_success_status_code, - "PutBucketTagging_non_existing_bucket": PutBucketTagging_non_existing_bucket, - "PutBucketTagging_long_tags": PutBucketTagging_long_tags, - "PutBucketTagging_success": PutBucketTagging_success, - "GetBucketTagging_non_existing_bucket": GetBucketTagging_non_existing_bucket, - "GetBucketTagging_success": GetBucketTagging_success, - "DeleteBucketTagging_non_existing_object": DeleteBucketTagging_non_existing_object, - "DeleteBucketTagging_success_status": DeleteBucketTagging_success_status, - "DeleteBucketTagging_success": DeleteBucketTagging_success, - "PutObject_non_existing_bucket": PutObject_non_existing_bucket, - "PutObject_special_chars": PutObject_special_chars, - "PutObject_invalid_long_tags": PutObject_invalid_long_tags, - "PutObject_success": PutObject_success, - "HeadObject_non_existing_object": HeadObject_non_existing_object, - "HeadObject_success": HeadObject_success, - "GetObject_non_existing_key": GetObject_non_existing_key, - "GetObject_invalid_ranges": GetObject_invalid_ranges, - "GetObject_with_meta": GetObject_with_meta, - "GetObject_success": GetObject_success, - "GetObject_by_range_success": GetObject_by_range_success, - "ListObjects_non_existing_bucket": ListObjects_non_existing_bucket, - "ListObjects_with_prefix": ListObjects_with_prefix, - "ListObject_truncated": ListObject_truncated, - "ListObjects_invalid_max_keys": ListObjects_invalid_max_keys, - "ListObjects_max_keys_0": ListObjects_max_keys_0, - "ListObjects_delimiter": ListObjects_delimiter, - "ListObjects_max_keys_none": ListObjects_max_keys_none, - "ListObjects_marker_not_from_obj_list": ListObjects_marker_not_from_obj_list, - "DeleteObject_non_existing_object": DeleteObject_non_existing_object, - "DeleteObject_success": DeleteObject_success, - "DeleteObject_success_status_code": DeleteObject_success_status_code, - "DeleteObjects_empty_input": DeleteObjects_empty_input, - "DeleteObjects_non_existing_objects": DeleteObjects_non_existing_objects, - "DeleteObjects_success": DeleteObjects_success, - "CopyObject_non_existing_dst_bucket": CopyObject_non_existing_dst_bucket, - "CopyObject_not_owned_source_bucket": CopyObject_not_owned_source_bucket, - "CopyObject_copy_to_itself": CopyObject_copy_to_itself, - "CopyObject_to_itself_with_new_metadata": CopyObject_to_itself_with_new_metadata, - "CopyObject_success": CopyObject_success, - "PutObjectTagging_non_existing_object": PutObjectTagging_non_existing_object, - "PutObjectTagging_long_tags": PutObjectTagging_long_tags, - "PutObjectTagging_success": PutObjectTagging_success, - "GetObjectTagging_non_existing_object": GetObjectTagging_non_existing_object, - "GetObjectTagging_success": GetObjectTagging_success, - "DeleteObjectTagging_non_existing_object": DeleteObjectTagging_non_existing_object, - "DeleteObjectTagging_success_status": DeleteObjectTagging_success_status, - "DeleteObjectTagging_success": DeleteObjectTagging_success, - "CreateMultipartUpload_non_existing_bucket": CreateMultipartUpload_non_existing_bucket, - "CreateMultipartUpload_success": CreateMultipartUpload_success, - "UploadPart_non_existing_bucket": UploadPart_non_existing_bucket, - "UploadPart_invalid_part_number": UploadPart_invalid_part_number, - "UploadPart_non_existing_key": UploadPart_non_existing_key, - "UploadPart_non_existing_mp_upload": UploadPart_non_existing_mp_upload, - "UploadPart_success": UploadPart_success, - "UploadPartCopy_non_existing_bucket": UploadPartCopy_non_existing_bucket, - "UploadPartCopy_incorrect_uploadId": UploadPartCopy_incorrect_uploadId, - "UploadPartCopy_incorrect_object_key": UploadPartCopy_incorrect_object_key, - "UploadPartCopy_invalid_part_number": UploadPartCopy_invalid_part_number, - "UploadPartCopy_invalid_copy_source": UploadPartCopy_invalid_copy_source, - "UploadPartCopy_non_existing_source_bucket": UploadPartCopy_non_existing_source_bucket, - "UploadPartCopy_non_existing_source_object_key": UploadPartCopy_non_existing_source_object_key, - "UploadPartCopy_success": UploadPartCopy_success, - "UploadPartCopy_by_range_invalid_range": UploadPartCopy_by_range_invalid_range, - "UploadPartCopy_greater_range_than_obj_size": UploadPartCopy_greater_range_than_obj_size, - "UploadPartCopy_by_range_success": UploadPartCopy_by_range_success, - "ListParts_incorrect_uploadId": ListParts_incorrect_uploadId, - "ListParts_incorrect_object_key": ListParts_incorrect_object_key, - "ListParts_success": ListParts_success, - "ListMultipartUploads_non_existing_bucket": ListMultipartUploads_non_existing_bucket, - "ListMultipartUploads_empty_result": ListMultipartUploads_empty_result, - "ListMultipartUploads_invalid_max_uploads": ListMultipartUploads_invalid_max_uploads, - "ListMultipartUploads_max_uploads": ListMultipartUploads_max_uploads, - "ListMultipartUploads_incorrect_next_key_marker": ListMultipartUploads_incorrect_next_key_marker, - "ListMultipartUploads_ignore_upload_id_marker": ListMultipartUploads_ignore_upload_id_marker, - "ListMultipartUploads_success": ListMultipartUploads_success, - "AbortMultipartUpload_non_existing_bucket": AbortMultipartUpload_non_existing_bucket, - "AbortMultipartUpload_incorrect_uploadId": AbortMultipartUpload_incorrect_uploadId, - "AbortMultipartUpload_incorrect_object_key": AbortMultipartUpload_incorrect_object_key, - "AbortMultipartUpload_success": AbortMultipartUpload_success, - "AbortMultipartUpload_success_status_code": AbortMultipartUpload_success_status_code, - "CompletedMultipartUpload_non_existing_bucket": CompletedMultipartUpload_non_existing_bucket, - "CompleteMultipartUpload_invalid_part_number": CompleteMultipartUpload_invalid_part_number, - "CompleteMultipartUpload_invalid_ETag": CompleteMultipartUpload_invalid_ETag, - "CompleteMultipartUpload_success": CompleteMultipartUpload_success, - "PutBucketAcl_non_existing_bucket": PutBucketAcl_non_existing_bucket, - "PutBucketAcl_invalid_acl_canned_and_acp": PutBucketAcl_invalid_acl_canned_and_acp, - "PutBucketAcl_invalid_acl_canned_and_grants": PutBucketAcl_invalid_acl_canned_and_grants, - "PutBucketAcl_invalid_acl_acp_and_grants": PutBucketAcl_invalid_acl_acp_and_grants, - "PutBucketAcl_invalid_owner": PutBucketAcl_invalid_owner, - "PutBucketAcl_success_access_denied": PutBucketAcl_success_access_denied, - "PutBucketAcl_success_grants": PutBucketAcl_success_grants, - "PutBucketAcl_success_canned_acl": PutBucketAcl_success_canned_acl, - "PutBucketAcl_success_acp": PutBucketAcl_success_acp, - "GetBucketAcl_non_existing_bucket": GetBucketAcl_non_existing_bucket, - "GetBucketAcl_access_denied": GetBucketAcl_access_denied, - "GetBucketAcl_success": GetBucketAcl_success, - "PutObject_overwrite_dir_obj": PutObject_overwrite_dir_obj, - "PutObject_overwrite_file_obj": PutObject_overwrite_file_obj, - "PutObject_dir_obj_with_data": PutObject_dir_obj_with_data, - "CreateMultipartUpload_dir_obj": CreateMultipartUpload_dir_obj, + "Authentication_empty_auth_header": Authentication_empty_auth_header, + "Authentication_invalid_auth_header": Authentication_invalid_auth_header, + "Authentication_unsupported_signature_version": Authentication_unsupported_signature_version, + "Authentication_malformed_credentials": Authentication_malformed_credentials, + "Authentication_malformed_credentials_invalid_parts": Authentication_malformed_credentials_invalid_parts, + "Authentication_credentials_terminated_string": Authentication_credentials_terminated_string, + "Authentication_credentials_incorrect_service": Authentication_credentials_incorrect_service, + "Authentication_credentials_incorrect_region": Authentication_credentials_incorrect_region, + "Authentication_credentials_invalid_date": Authentication_credentials_invalid_date, + "Authentication_credentials_future_date": Authentication_credentials_future_date, + "Authentication_credentials_past_date": Authentication_credentials_past_date, + "Authentication_credentials_non_existing_access_key": Authentication_credentials_non_existing_access_key, + "Authentication_invalid_signed_headers": Authentication_invalid_signed_headers, + "Authentication_missing_date_header": Authentication_missing_date_header, + "Authentication_invalid_date_header": Authentication_invalid_date_header, + "Authentication_date_mismatch": Authentication_date_mismatch, + "Authentication_incorrect_payload_hash": Authentication_incorrect_payload_hash, + "Authentication_incorrect_md5": Authentication_incorrect_md5, + "Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key, + "CreateBucket_invalid_bucket_name": CreateBucket_invalid_bucket_name, + "CreateBucket_existing_bucket": CreateBucket_existing_bucket, + "CreateBucket_as_user": CreateBucket_as_user, + "CreateDeleteBucket_success": CreateDeleteBucket_success, + "CreateBucket_default_acl": CreateBucket_default_acl, + "CreateBucket_non_default_acl": CreateBucket_non_default_acl, + "HeadBucket_non_existing_bucket": HeadBucket_non_existing_bucket, + "HeadBucket_success": HeadBucket_success, + "ListBuckets_as_user": ListBuckets_as_user, + "ListBuckets_as_admin": ListBuckets_as_admin, + "ListBuckets_success": ListBuckets_success, + "DeleteBucket_non_existing_bucket": DeleteBucket_non_existing_bucket, + "DeleteBucket_non_empty_bucket": DeleteBucket_non_empty_bucket, + "DeleteBucket_success_status_code": DeleteBucket_success_status_code, + "PutBucketTagging_non_existing_bucket": PutBucketTagging_non_existing_bucket, + "PutBucketTagging_long_tags": PutBucketTagging_long_tags, + "PutBucketTagging_success": PutBucketTagging_success, + "GetBucketTagging_non_existing_bucket": GetBucketTagging_non_existing_bucket, + "GetBucketTagging_success": GetBucketTagging_success, + "DeleteBucketTagging_non_existing_object": DeleteBucketTagging_non_existing_object, + "DeleteBucketTagging_success_status": DeleteBucketTagging_success_status, + "DeleteBucketTagging_success": DeleteBucketTagging_success, + "PutObject_non_existing_bucket": PutObject_non_existing_bucket, + "PutObject_special_chars": PutObject_special_chars, + "PutObject_invalid_long_tags": PutObject_invalid_long_tags, + "PutObject_success": PutObject_success, + "HeadObject_non_existing_object": HeadObject_non_existing_object, + "HeadObject_success": HeadObject_success, + "GetObject_non_existing_key": GetObject_non_existing_key, + "GetObject_invalid_ranges": GetObject_invalid_ranges, + "GetObject_with_meta": GetObject_with_meta, + "GetObject_success": GetObject_success, + "GetObject_by_range_success": GetObject_by_range_success, + "ListObjects_non_existing_bucket": ListObjects_non_existing_bucket, + "ListObjects_with_prefix": ListObjects_with_prefix, + "ListObject_truncated": ListObject_truncated, + "ListObjects_invalid_max_keys": ListObjects_invalid_max_keys, + "ListObjects_max_keys_0": ListObjects_max_keys_0, + "ListObjects_delimiter": ListObjects_delimiter, + "ListObjects_max_keys_none": ListObjects_max_keys_none, + "ListObjects_marker_not_from_obj_list": ListObjects_marker_not_from_obj_list, + "ListObjectsV2_start_after": ListObjectsV2_start_after, + "ListObjectsV2_both_start_after_and_continuation_token": ListObjectsV2_both_start_after_and_continuation_token, + "ListObjectsV2_start_after_not_in_list": ListObjectsV2_start_after_not_in_list, + "ListObjectsV2_start_after_empty_result": ListObjectsV2_start_after_empty_result, + "DeleteObject_non_existing_object": DeleteObject_non_existing_object, + "DeleteObject_success": DeleteObject_success, + "DeleteObject_success_status_code": DeleteObject_success_status_code, + "DeleteObjects_empty_input": DeleteObjects_empty_input, + "DeleteObjects_non_existing_objects": DeleteObjects_non_existing_objects, + "DeleteObjects_success": DeleteObjects_success, + "CopyObject_non_existing_dst_bucket": CopyObject_non_existing_dst_bucket, + "CopyObject_not_owned_source_bucket": CopyObject_not_owned_source_bucket, + "CopyObject_copy_to_itself": CopyObject_copy_to_itself, + "CopyObject_to_itself_with_new_metadata": CopyObject_to_itself_with_new_metadata, + "CopyObject_success": CopyObject_success, + "PutObjectTagging_non_existing_object": PutObjectTagging_non_existing_object, + "PutObjectTagging_long_tags": PutObjectTagging_long_tags, + "PutObjectTagging_success": PutObjectTagging_success, + "GetObjectTagging_non_existing_object": GetObjectTagging_non_existing_object, + "GetObjectTagging_success": GetObjectTagging_success, + "DeleteObjectTagging_non_existing_object": DeleteObjectTagging_non_existing_object, + "DeleteObjectTagging_success_status": DeleteObjectTagging_success_status, + "DeleteObjectTagging_success": DeleteObjectTagging_success, + "CreateMultipartUpload_non_existing_bucket": CreateMultipartUpload_non_existing_bucket, + "CreateMultipartUpload_success": CreateMultipartUpload_success, + "UploadPart_non_existing_bucket": UploadPart_non_existing_bucket, + "UploadPart_invalid_part_number": UploadPart_invalid_part_number, + "UploadPart_non_existing_key": UploadPart_non_existing_key, + "UploadPart_non_existing_mp_upload": UploadPart_non_existing_mp_upload, + "UploadPart_success": UploadPart_success, + "UploadPartCopy_non_existing_bucket": UploadPartCopy_non_existing_bucket, + "UploadPartCopy_incorrect_uploadId": UploadPartCopy_incorrect_uploadId, + "UploadPartCopy_incorrect_object_key": UploadPartCopy_incorrect_object_key, + "UploadPartCopy_invalid_part_number": UploadPartCopy_invalid_part_number, + "UploadPartCopy_invalid_copy_source": UploadPartCopy_invalid_copy_source, + "UploadPartCopy_non_existing_source_bucket": UploadPartCopy_non_existing_source_bucket, + "UploadPartCopy_non_existing_source_object_key": UploadPartCopy_non_existing_source_object_key, + "UploadPartCopy_success": UploadPartCopy_success, + "UploadPartCopy_by_range_invalid_range": UploadPartCopy_by_range_invalid_range, + "UploadPartCopy_greater_range_than_obj_size": UploadPartCopy_greater_range_than_obj_size, + "UploadPartCopy_by_range_success": UploadPartCopy_by_range_success, + "ListParts_incorrect_uploadId": ListParts_incorrect_uploadId, + "ListParts_incorrect_object_key": ListParts_incorrect_object_key, + "ListParts_success": ListParts_success, + "ListMultipartUploads_non_existing_bucket": ListMultipartUploads_non_existing_bucket, + "ListMultipartUploads_empty_result": ListMultipartUploads_empty_result, + "ListMultipartUploads_invalid_max_uploads": ListMultipartUploads_invalid_max_uploads, + "ListMultipartUploads_max_uploads": ListMultipartUploads_max_uploads, + "ListMultipartUploads_incorrect_next_key_marker": ListMultipartUploads_incorrect_next_key_marker, + "ListMultipartUploads_ignore_upload_id_marker": ListMultipartUploads_ignore_upload_id_marker, + "ListMultipartUploads_success": ListMultipartUploads_success, + "AbortMultipartUpload_non_existing_bucket": AbortMultipartUpload_non_existing_bucket, + "AbortMultipartUpload_incorrect_uploadId": AbortMultipartUpload_incorrect_uploadId, + "AbortMultipartUpload_incorrect_object_key": AbortMultipartUpload_incorrect_object_key, + "AbortMultipartUpload_success": AbortMultipartUpload_success, + "AbortMultipartUpload_success_status_code": AbortMultipartUpload_success_status_code, + "CompletedMultipartUpload_non_existing_bucket": CompletedMultipartUpload_non_existing_bucket, + "CompleteMultipartUpload_invalid_part_number": CompleteMultipartUpload_invalid_part_number, + "CompleteMultipartUpload_invalid_ETag": CompleteMultipartUpload_invalid_ETag, + "CompleteMultipartUpload_success": CompleteMultipartUpload_success, + "PutBucketAcl_non_existing_bucket": PutBucketAcl_non_existing_bucket, + "PutBucketAcl_invalid_acl_canned_and_acp": PutBucketAcl_invalid_acl_canned_and_acp, + "PutBucketAcl_invalid_acl_canned_and_grants": PutBucketAcl_invalid_acl_canned_and_grants, + "PutBucketAcl_invalid_acl_acp_and_grants": PutBucketAcl_invalid_acl_acp_and_grants, + "PutBucketAcl_invalid_owner": PutBucketAcl_invalid_owner, + "PutBucketAcl_success_access_denied": PutBucketAcl_success_access_denied, + "PutBucketAcl_success_grants": PutBucketAcl_success_grants, + "PutBucketAcl_success_canned_acl": PutBucketAcl_success_canned_acl, + "PutBucketAcl_success_acp": PutBucketAcl_success_acp, + "GetBucketAcl_non_existing_bucket": GetBucketAcl_non_existing_bucket, + "GetBucketAcl_access_denied": GetBucketAcl_access_denied, + "GetBucketAcl_success": GetBucketAcl_success, + "PutObject_overwrite_dir_obj": PutObject_overwrite_dir_obj, + "PutObject_overwrite_file_obj": PutObject_overwrite_file_obj, + "PutObject_dir_obj_with_data": PutObject_dir_obj_with_data, + "CreateMultipartUpload_dir_obj": CreateMultipartUpload_dir_obj, } } diff --git a/integration/tests.go b/integration/tests.go index 1775e9c..0a7a778 100644 --- a/integration/tests.go +++ b/integration/tests.go @@ -1917,6 +1917,138 @@ func ListObjects_marker_not_from_obj_list(s *S3Conf) error { }) } +func ListObjectsV2_start_after(s *S3Conf) error { + testName := "ListObjectsV2_start_after" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + err := putObjects(s3client, []string{"foo", "bar", "baz"}, bucket) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &bucket, + StartAfter: getPtr("bar"), + }) + cancel() + if err != nil { + return err + } + + if !compareObjects([]string{"baz", "foo"}, out.Contents) { + return fmt.Errorf("expected output to be %v, instead got %v", []string{"baz", "foo"}, out.Contents) + } + + return nil + }) +} + +func ListObjectsV2_both_start_after_and_continuation_token(s *S3Conf) error { + testName := "ListObjectsV2_both_start_after_and_continuation_token" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + err := putObjects(s3client, []string{"foo", "bar", "baz", "quxx"}, bucket) + if err != nil { + return err + } + var maxKeys int32 = 1 + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &bucket, + MaxKeys: &maxKeys, + }) + cancel() + if err != nil { + return err + } + + if out.IsTruncated == nil || !*out.IsTruncated { + return fmt.Errorf("expected output to be truncated") + } + + if *out.MaxKeys != maxKeys { + return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, out.MaxKeys) + } + + if *out.NextContinuationToken != "bar" { + return fmt.Errorf("expected next-marker to be baz, instead got %v", *out.NextContinuationToken) + } + + if !compareObjects([]string{"bar"}, out.Contents) { + return fmt.Errorf("unexpected output for list objects with max-keys") + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + resp, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &bucket, + ContinuationToken: out.NextContinuationToken, + StartAfter: getPtr("baz"), + }) + cancel() + if err != nil { + return err + } + + if !compareObjects([]string{"foo", "quxx"}, resp.Contents) { + return fmt.Errorf("unexpected output for list objects with max-keys") + } + + return nil + }) +} + +func ListObjectsV2_start_after_not_in_list(s *S3Conf) error { + testName := "ListObjectsV2_start_after_not_in_list" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + err := putObjects(s3client, []string{"foo", "bar", "baz", "quxx"}, bucket) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &bucket, + StartAfter: getPtr("blah"), + }) + cancel() + if err != nil { + return err + } + + if !compareObjects([]string{"foo", "quxx"}, out.Contents) { + return fmt.Errorf("expected output to be %v, instead got %v", []string{"foo", "quxx"}, out.Contents) + } + + return nil + }) +} + +func ListObjectsV2_start_after_empty_result(s *S3Conf) error { + testName := "ListObjectsV2_start_after_empty_result" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + err := putObjects(s3client, []string{"foo", "bar", "baz", "quxx"}, bucket) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &bucket, + StartAfter: getPtr("zzz"), + }) + cancel() + if err != nil { + return err + } + + if len(out.Contents) != 0 { + return fmt.Errorf("expected empty output instead got %v", out.Contents) + } + + return nil + }) +} + func DeleteObject_non_existing_object(s *S3Conf) error { testName := "DeleteObject_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index e988118..2063ea1 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -236,6 +236,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { bucket := ctx.Params("bucket") prefix := ctx.Query("prefix") cToken := ctx.Query("continuation-token") + sAfter := ctx.Query("start-after") marker := ctx.Query("marker") delimiter := ctx.Query("delimiter") maxkeysStr := ctx.Query("max-keys") @@ -319,6 +320,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { ContinuationToken: &cToken, Delimiter: &delimiter, MaxKeys: &maxkeys, + StartAfter: &sAfter, }) return SendXMLResponse(ctx, res, err, &MetaOpts{Logger: c.logger, Action: "ListObjectsV2", BucketOwner: parsedAcl.Owner}) }