Upstream versity/versitygw state of the touched files before eecdc6b996

This commit is contained in:
upstream committed 2026-10-02 16:01:08 +02:00
1 parent e0fef79b7b
commit 6704314fea
5 files changed
+2892 -2

No files matched your search

+1560
View File
File diff suppressed because it is too large. Load diff
+381
View File
@@ -0,0 +1,381 @@
// Copyright 2026 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package s3err
import (
"bytes"
"encoding/xml"
"fmt"
"net/http"
)
type InvalidArgErrorCode int
const (
InvalidArgMaxBuckets InvalidArgErrorCode = iota
InvalidArgNegativeMaxKeys
InvalidArgObjectAttributes
InvalidArgPartNumber
InvalidArgCompleteMpPartNumber
InvalidArgCopySourceRange
InvalidArgCopySourceBucket
InvalidArgCopySourceObject
InvalidArgCopySourceEncoding
InvalidArgURLEncodedTagging
InvalidArgAuthHeader
InvalidArgAuthorizationType
InvalidArgPOSTFileRequired
InvalidArgSHA256Payload
InvalidArgCopySource
InvalidArgRetainUntilDate
InvalidArgPastObjectLockRetainDate
InvalidArgObjectLockRetentionDays
InvalidArgObjectLockRetentionYears
InvalidArgObjectLockRetentionDaysTooLarge
InvalidArgObjectLockRetentionYearsTooLarge
InvalidArgMissingObjectLockRetainDate
InvalidArgMissingObjectLockMode
InvalidArgLegalHoldStatus
InvalidArgObjectLockMode
InvalidArgAnonymousObjectLock
InvalidArgMetadataDirective
InvalidArgTaggingDirective
InvalidArgVersionId
InvalidArgEmptyVersionId
InvalidArgChecksumPart
InvalidArgMissingUploadId
InvalidArgUploadIdMarker
InvalidArgCannedAcl
InvalidArgOnlyAws4HmacSha256
InvalidArgDateHeader
InvalidArgIndexDocumentSuffix
InvalidArgMissingIndexDocumentSuffix
InvalidArgErrorDocumentKey
InvalidArgAwsChunkedUnsignedPayload
InvalidArgSSECMissingAlgorithm
InvalidArgSSECMissingKey
InvalidArgSSECMissingKeyMD5
InvalidArgSSECInvalidKey
InvalidArgSSECKeyMD5Mismatch
InvalidArgSSECInvalidAlgorithm
InvalidArgSSECIncompatibleEncryption
InvalidArgSSEInvalidEncryptionMethod
InvalidArgSSECRequiresTLS
)
var invalidArgErrResponses = map[InvalidArgErrorCode]InvalidArgumentError{
InvalidArgMaxBuckets: {
Description: "Argument max-buckets must be an integer between 1 and 10000.",
ArgumentName: "max-buckets",
},
InvalidArgNegativeMaxKeys: {
Description: "max-keys cannot be negative",
ArgumentName: "maxKeys",
},
InvalidArgObjectAttributes: {
Description: "Invalid attribute name specified.",
ArgumentName: "x-amz-object-attributes",
},
InvalidArgPartNumber: {
Description: "Part number must be an integer between 1 and 10000, inclusive.",
ArgumentName: "partNumber",
},
InvalidArgCompleteMpPartNumber: {
Description: "PartNumber must be >= 1",
ArgumentName: "PartNumber",
},
InvalidArgCopySourceRange: {
Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy",
ArgumentName: "x-amz-copy-source-range",
},
InvalidArgCopySourceBucket: {
Description: "Invalid copy source bucket name",
ArgumentName: "x-amz-copy-source",
},
InvalidArgCopySourceObject: {
Description: "Invalid copy source object key",
ArgumentName: "x-amz-copy-source",
},
InvalidArgCopySourceEncoding: {
Description: "Invalid copy source encoding",
ArgumentName: "x-amz-copy-source",
},
InvalidArgURLEncodedTagging: {
Description: "The header 'x-amz-tagging' shall be encoded as UTF-8 then URLEncoded URL query parameters without tag name duplicates.",
ArgumentName: "x-amz-tagging",
},
InvalidArgAuthHeader: {
Description: "Authorization header is invalid -- one and only one ' ' (space) required.",
ArgumentName: "Authorization",
},
InvalidArgAuthorizationType: {
Description: "Unsupported Authorization Type",
ArgumentName: "Authorization",
},
InvalidArgPOSTFileRequired: {
Description: "POST requires exactly one file upload per request.",
ArgumentName: "file",
},
InvalidArgSHA256Payload: {
Description: "x-amz-content-sha256 must be UNSIGNED-PAYLOAD, STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER or a valid sha256 value.",
ArgumentName: "x-amz-content-sha256",
},
InvalidArgAwsChunkedUnsignedPayload: {
Description: "aws-chunked encoding is not supported when x-amz-content-sha256 UNSIGNED-PAYLOAD is supplied.",
ArgumentName: "Content-Encoding",
},
InvalidArgCopySource: {
Description: "You can only specify a copy source header for copy requests.",
ArgumentName: "x-amz-copy-source",
},
InvalidArgRetainUntilDate: {
Description: "The retain until date must be provided in ISO 8601 format",
ArgumentName: "x-amz-object-lock-retain-until-date",
},
InvalidArgPastObjectLockRetainDate: {
Description: "The retain until date must be in the future!",
ArgumentName: "x-amz-object-lock-retain-until-date",
},
InvalidArgMissingObjectLockRetainDate: {
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied",
ArgumentName: "x-amz-object-lock-retain-until-date",
},
InvalidArgMissingObjectLockMode: {
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied",
ArgumentName: "x-amz-object-lock-mode",
},
InvalidArgObjectLockRetentionDays: {
Description: "Default retention period must be a positive integer value.",
ArgumentName: "Days",
},
InvalidArgObjectLockRetentionYears: {
Description: "Default retention period must be a positive integer value.",
ArgumentName: "Years",
},
InvalidArgObjectLockRetentionDaysTooLarge: {
Description: "Default retention period too large.",
ArgumentName: "Days",
},
InvalidArgObjectLockRetentionYearsTooLarge: {
Description: "Default retention period too large.",
ArgumentName: "Years",
},
InvalidArgLegalHoldStatus: {
Description: "Legal Hold must be either of 'ON' or 'OFF'",
ArgumentName: "x-amz-object-lock-legal-hold",
},
InvalidArgObjectLockMode: {
Description: "Unknown wormMode directive.",
ArgumentName: "x-amz-object-lock-mode",
},
InvalidArgAnonymousObjectLock: {
Description: "Put Object requests with Object Lock parameters require AWS Signature Version 4",
ArgumentName: "Authorization",
},
InvalidArgMetadataDirective: {
Description: "Unknown metadata directive.",
ArgumentName: "x-amz-metadata-directive",
},
InvalidArgTaggingDirective: {
Description: "Unknown tagging directive.",
ArgumentName: "x-amz-tagging-directive",
},
InvalidArgVersionId: {
Description: "Invalid version id specified",
ArgumentName: "versionId",
},
InvalidArgEmptyVersionId: {
Description: "Version id cannot be the empty string",
ArgumentName: "versionId",
},
InvalidArgChecksumPart: {
Description: "Invalid Base64 or multiple checksums present in request",
ArgumentName: "Checksum",
},
InvalidArgMissingUploadId: {
Description: "This operation does not accept partNumber without uploadId",
ArgumentName: "partNumber",
},
InvalidArgUploadIdMarker: {
Description: "Invalid uploadId marker",
ArgumentName: "upload-id-marker",
},
InvalidArgCannedAcl: {
Description: "",
ArgumentName: "x-amz-acl",
},
InvalidArgOnlyAws4HmacSha256: {
Description: "Only AWS4-HMAC-SHA256 is supported",
ArgumentName: "X-Amz-Algorithm",
},
InvalidArgDateHeader: {
Description: "X-Amz-Date must be formated via ISO8601 Long format",
ArgumentName: "X-Amz-Date",
},
InvalidArgIndexDocumentSuffix: {
Description: "The IndexDocument Suffix is not well formed",
ArgumentName: "IndexDocument",
},
InvalidArgMissingIndexDocumentSuffix: {
Description: "A value for IndexDocument Suffix must be provided if RedirectAllRequestsTo is empty",
ArgumentName: "IndexDocument",
},
InvalidArgErrorDocumentKey: {
Description: "The ErrorDocument Key is not well formed",
ArgumentName: "ErrorDocument",
},
InvalidArgSSECMissingAlgorithm: {
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSECMissingKey: {
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide an appropriate secret key.",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSECMissingKeyMD5: {
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide the client calculated MD5 of the secret key.",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSECInvalidKey: {
Description: "The secret key was invalid for the specified algorithm.",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSECKeyMD5Mismatch: {
Description: "The calculated MD5 hash of the key did not match the hash that was provided.",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSECInvalidAlgorithm: {
Description: "The Encryption request you specified is not valid. Supported value: AES256.",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSECIncompatibleEncryption: {
Description: "Server Side Encryption with Customer provided key is incompatible with the encryption method specified",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSEInvalidEncryptionMethod: {
Description: "The encryption method specified is not valid.",
ArgumentName: "x-amz-server-side-encryption",
},
InvalidArgSSECRequiresTLS: {
Description: "Requests specifying Server Side Encryption with Customer provided keys must be made over a secure connection.",
ArgumentName: "x-amz-server-side-encryption",
},
}
// InvalidArgumentError is returned when a request argument is invalid.
// Produces <ArgumentName> and <ArgumentValue> fields in the XML response, and
// a <Region> field when the expected gateway region is known.
type InvalidArgumentError struct {
Description string
ArgumentName string
ArgumentValue string
Region string `xml:",omitempty"`
}
func (e InvalidArgumentError) BaseError() APIError {
return APIError{
Code: "InvalidArgument",
Description: e.Description,
HTTPStatusCode: http.StatusBadRequest,
}
}
// InvalidArgumentError http status code is always 400
func (e InvalidArgumentError) StatusCode() int { return http.StatusBadRequest }
func (e InvalidArgumentError) Error() string {
var bytesBuffer bytes.Buffer
bytesBuffer.WriteString(xml.Header)
enc := xml.NewEncoder(&bytesBuffer)
_ = enc.Encode(e)
return bytesBuffer.String()
}
func (e InvalidArgumentError) XMLBody(requestID, hostID string) []byte {
return encodeResponse(struct {
XMLName xml.Name `xml:"Error"`
Code string
Message string
ArgumentName string `xml:"ArgumentName,omitempty"`
ArgumentValue string `xml:"ArgumentValue,omitempty"`
Region string `xml:"Region,omitempty"`
RequestId string `xml:"RequestId,omitempty"`
HostId string `xml:"HostId,omitempty"`
}{
Code: "InvalidArgument",
Message: e.Description,
ArgumentName: e.ArgumentName,
ArgumentValue: e.ArgumentValue,
Region: e.Region,
RequestId: requestID,
HostId: hostID,
})
}
func (e InvalidArgumentError) HTMLBody(requestID, hostID string) []byte {
fields := []ErrorField{
{Name: "ArgumentName", Value: e.ArgumentName},
{Name: "ArgumentValue", Value: e.ArgumentValue},
}
// Region is only set for the credential region mismatch; every other
// invalid argument error leaves it out.
if e.Region != "" {
fields = append(fields, ErrorField{Name: "Region", Value: e.Region})
}
return e.BaseError().encodeHTMLResponse(requestID, hostID, fields...)
}
// ExpectedRegion implements RegionMismatchError. Only the credential region
// mismatch sets Region; every other invalid argument error returns "".
func (e InvalidArgumentError) ExpectedRegion() string { return e.Region }
func GetInvalidArgumentErr(code InvalidArgErrorCode, value string) InvalidArgumentError {
err := invalidArgErrResponses[code]
err.ArgumentValue = value
return err
}
func GetInvalidArgMaxLimiter(name, value string) InvalidArgumentError {
return InvalidArgumentError{
ArgumentName: name,
ArgumentValue: value,
Description: fmt.Sprintf("Provided %s not an integer or within integer range", value),
}
}
func GetInvalidArgNegativeMaxLimiter(name, value string) InvalidArgumentError {
return InvalidArgumentError{
ArgumentName: name,
ArgumentValue: value,
Description: fmt.Sprintf("Argument %s must be an integer between 0 and 2147483647", value),
}
}
func GetInvalidArgExceedingRange(size int64) InvalidArgumentError {
return InvalidArgumentError{
ArgumentName: "x-amz-copy-source-range",
ArgumentValue: fmt.Sprint(size),
Description: fmt.Sprintf("Range specified is not valid for source object of size: %d", size),
}
}
func GetInvalidArgObjectOwnership(value string) InvalidArgumentError {
return InvalidArgumentError{
ArgumentName: "x-amz-object-ownership",
// no ArgumentValue is returned for this error
Description: fmt.Sprintf("Invalid x-amz-object-ownership header: %s", value),
}
}
+674
View File
@@ -0,0 +1,674 @@
// Copyright 2023 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package integration
import (
"context"
"fmt"
"sort"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/google/uuid"
"github.com/versity/versitygw/s3err"
)
func ListMultipartUploads_non_existing_bucket(s *S3Conf) error {
testName := "ListMultipartUploads_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
bucketName := getBucketName()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucketName,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func ListMultipartUploads_empty_result(s *S3Conf) error {
testName := "ListMultipartUploads_empty_result"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if len(out.Uploads) != 0 {
return fmt.Errorf("expected empty uploads, instead got %+v",
out.Uploads)
}
return nil
})
}
func ListMultipartUploads_invalid_max_uploads(s *S3Conf) error {
testName := "ListMultipartUploads_invalid_max_uploads"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
maxUploads := int32(-3)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
MaxUploads: &maxUploads,
})
cancel()
if err := checkApiErr(err, s3err.GetInvalidArgNegativeMaxLimiter("max-uploads", fmt.Sprint(maxUploads))); err != nil {
return err
}
return nil
})
}
func ListMultipartUploads_max_uploads(s *S3Conf) error {
testName := "ListMultipartUploads_max_uploads"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := []types.MultipartUpload{}
for i := 1; i < 6; i++ {
out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i))
if err != nil {
return err
}
uploads = append(uploads, types.MultipartUpload{
UploadId: out.UploadId,
Key: out.Key,
StorageClass: types.StorageClassStandard,
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
maxUploads := int32(2)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
MaxUploads: &maxUploads,
})
cancel()
if err != nil {
return err
}
if out.IsTruncated == nil {
return fmt.Errorf("unexpected nil is-truncated")
}
if out.MaxUploads == nil {
return fmt.Errorf("unexpected nil max-uploads")
}
if !*out.IsTruncated {
return fmt.Errorf("expected the output to be truncated")
}
if *out.MaxUploads != 2 {
return fmt.Errorf("expected max-uploads to be 2, instead got %v",
out.MaxUploads)
}
if ok := compareMultipartUploads(out.Uploads, uploads[:2]); !ok {
return fmt.Errorf("expected multipart uploads to be %v, instead got %v",
uploads[:2], out.Uploads)
}
if getString(out.NextKeyMarker) != getString(uploads[1].Key) {
return fmt.Errorf("expected next-key-marker to be %v, instead got %v",
getString(uploads[1].Key), getString(out.NextKeyMarker))
}
if getString(out.NextUploadIdMarker) != getString(uploads[1].UploadId) {
return fmt.Errorf("expected next-upload-id-marker to be %v, instead got %v",
getString(uploads[1].UploadId), getString(out.NextUploadIdMarker))
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: out.NextKeyMarker,
})
cancel()
if err != nil {
return err
}
if ok := compareMultipartUploads(out.Uploads, uploads[2:]); !ok {
return fmt.Errorf("expected multipart uploads to be %v, instead got %v",
uploads[2:], out.Uploads)
}
return nil
})
}
func ListMultipartUploads_exceeding_max_uploads(s *S3Conf) error {
testName := "ListMultipartUploads_exceeding_max_uploads"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
maxUploads := int32(1343235)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
MaxUploads: &maxUploads,
})
cancel()
if err != nil {
return err
}
if res.MaxUploads == nil {
return fmt.Errorf("unexpected nil max-uploads")
}
if *res.MaxUploads != 1000 {
return fmt.Errorf("expected max-uploads to be %v, instaed got %v",
1000, *res.MaxUploads)
}
return nil
})
}
func ListMultipartUploads_ignore_upload_id_marker(s *S3Conf) error {
testName := "ListMultipartUploads_ignore_upload_id_marker"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := []types.MultipartUpload{}
for i := 1; i < 6; i++ {
out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i))
if err != nil {
return err
}
uploads = append(uploads, types.MultipartUpload{
UploadId: out.UploadId,
Key: out.Key,
StorageClass: types.StorageClassStandard,
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
UploadIdMarker: uploads[2].UploadId,
})
cancel()
if err != nil {
return err
}
if !compareMultipartUploads(out.Uploads, uploads) {
return fmt.Errorf("expected multipart uploads to be %v, instead got %v",
uploads, out.Uploads)
}
// should ignore invalid uploaId marker
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
UploadIdMarker: getPtr("invalid_uploadId_marker"),
})
cancel()
if err != nil {
return err
}
if !compareMultipartUploads(out.Uploads, uploads) {
return fmt.Errorf("expected multipart uploads to be %v, instead got %v",
uploads, out.Uploads)
}
return nil
})
}
func ListMultipartUploads_invalid_uploadId_marker(s *S3Conf) error {
testName := "ListMultipartUploads_invalid_uploadId_marker"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := make([]types.MultipartUpload, 0, 5)
for i := range 5 {
out, err := createMp(s3client, bucket, fmt.Sprintf("obj-%v", i))
if err != nil {
return err
}
uploads = append(uploads, types.MultipartUpload{
UploadId: out.UploadId,
Key: out.Key,
StorageClass: types.StorageClassStandard,
})
}
// invalid UUID
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: getPtr("obj-2"),
UploadIdMarker: getPtr("invalid_uploadId_marker"),
})
cancel()
if err := checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgUploadIdMarker, "invalid_uploadId_marker")); err != nil {
return err
}
// valid UUID, but not from the list
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
uploadIDMarker := uuid.New().String()
_, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: getPtr("obj-2"),
UploadIdMarker: &uploadIDMarker,
})
cancel()
if err := checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgUploadIdMarker, uploadIDMarker)); err != nil {
return err
}
// uploadId marker and key marker mismatch
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: getPtr("obj-2"),
UploadIdMarker: uploads[4].UploadId,
})
cancel()
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgUploadIdMarker, *uploads[4].UploadId))
})
}
func ListMultipartUploads_keyMarker_not_from_list(s *S3Conf) error {
testName := "ListMultipartUploads_keyMarker_not_from_list"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := make([]types.MultipartUpload, 0, 9)
for _, mp := range []struct {
key string
count int
}{
{"bar", 3},
{"baz", 4},
{"foo", 2},
} {
for range mp.count {
out, err := createMp(s3client, bucket, mp.key)
if err != nil {
return err
}
uploads = append(uploads, types.MultipartUpload{
Key: out.Key,
UploadId: out.UploadId,
StorageClass: types.StorageClassStandard,
})
// add an artificial delay, since some backends
// (e.g. azure, or posix on filesystems with coarse
// mtime resolution) can report the same identical
// creation/initiated time for uploads created back
// to back, making the resulting order non-deterministic
time.Sleep(time.Second)
}
}
// without uploadId marker
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: getPtr("bat"),
})
cancel()
if err != nil {
return err
}
if !compareMultipartUploads(uploads[3:], out.Uploads) {
return fmt.Errorf("expected the mp list to be %v, instead got %v", uploads[3:], out.Uploads)
}
// should start the listing after the specified uploadId marker
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: getPtr("bat"),
UploadIdMarker: uploads[4].UploadId,
})
cancel()
if err != nil {
return err
}
if !compareMultipartUploads(uploads[5:], out.Uploads) {
return fmt.Errorf("expected the mp list to be %v, instead got %v", uploads[5:], out.Uploads)
}
return nil
})
}
func ListMultipartUploads_delimiter_truncated(s *S3Conf) error {
testName := "ListMultipartUploads_delimiter_truncated"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := make([]types.MultipartUpload, 0, 6)
for _, key := range []string{
"abc/something",
"foo/bar/baz",
"foo/quxx",
"xyz/hello",
"zzz/bca",
"some/very/nested/mp/object",
} {
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,
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
Delimiter: getPtr("/"),
MaxUploads: getPtr(int32(2)),
})
cancel()
if err != nil {
return err
}
if len(out.Uploads) != 0 {
return fmt.Errorf("expected empty uplodas list, instead got %v", out.Uploads)
}
expectedCps := []string{"abc/", "foo/"}
if !comparePrefixes(expectedCps, out.CommonPrefixes) {
return fmt.Errorf("expected the common prefixes to be %v, instead got %v", expectedCps, out.CommonPrefixes)
}
if getString(out.NextKeyMarker) != "foo/" {
return fmt.Errorf("expected the next key marker to be 'foo/', instead got %s", getString(out.NextKeyMarker))
}
if getString(out.NextUploadIdMarker) != getString(uploads[1].UploadId) {
return fmt.Errorf("expected the next upload id marker to be %s, instead got %s", getString(uploads[1].UploadId), getString(out.NextUploadIdMarker))
}
if !*out.IsTruncated {
return fmt.Errorf("expected a truncated response")
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out2, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
Delimiter: getPtr("/"),
UploadIdMarker: out.NextUploadIdMarker,
KeyMarker: out.NextKeyMarker,
})
cancel()
if err != nil {
return err
}
if len(out2.Uploads) != 0 {
return fmt.Errorf("expected empty uplodas list, instead got %v", out2.Uploads)
}
expectedCps = []string{"foo/", "some/", "xyz/", "zzz/"}
if !comparePrefixes(expectedCps, out2.CommonPrefixes) {
return fmt.Errorf("expected the common prefixes to be %v, instead got %v", expectedCps, out2.CommonPrefixes)
}
if getString(out2.KeyMarker) != "foo/" {
return fmt.Errorf("expected key marker to be 'foo/', instead got %s", getString(out2.KeyMarker))
}
if getString(out2.UploadIdMarker) != getString(uploads[1].UploadId) {
return fmt.Errorf("expected the upload id marker to be %s, instead got %s", getString(uploads[1].UploadId), getString(out2.UploadIdMarker))
}
if getString(out2.NextKeyMarker) != "" {
return fmt.Errorf("expected empty next key marker, instead got %s", getString(out2.NextKeyMarker))
}
if getString(out2.NextUploadIdMarker) != "" {
return fmt.Errorf("expected empty next upload id marker, instead got %s", getString(out2.NextUploadIdMarker))
}
if *out2.IsTruncated {
return fmt.Errorf("expected a non-truncated response")
}
return nil
})
}
func ListMultipartUploads_prefix(s *S3Conf) error {
testName := "ListMultipartUploads_prefix"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
prefix := "foo"
uploads := make([]types.MultipartUpload, 0, 8)
for _, key := range []string{
"abc/something",
"foo/bar/baz",
"foo/quxx",
"hello/world",
"xyz/hello",
"zzz/bca",
"some/very/nested/mp/object",
"foo/xyz",
} {
out, err := createMp(s3client, bucket, key)
if err != nil {
return err
}
if strings.HasPrefix(key, prefix) {
uploads = append(uploads, types.MultipartUpload{
Key: out.Key,
UploadId: out.UploadId,
StorageClass: types.StorageClassStandard,
})
}
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
Prefix: &prefix,
})
cancel()
if err != nil {
return err
}
if getString(out.Prefix) != prefix {
return fmt.Errorf("expected the prefix to be %s, instead got %s", prefix, getString(out.Prefix))
}
if !compareMultipartUploads(out.Uploads, uploads) {
return fmt.Errorf("expected the uploads list to be %v, instead got %v", uploads, out.Uploads)
}
return nil
})
}
func ListMultipartUploads_both_delimiter_and_prefix(s *S3Conf) error {
testName := "ListMultipartUploads_both_delimiter_and_prefix"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
for _, key := range []string{
"foo/abc/bbb/aaa/c",
"abc/something",
"foo/bar/baz",
"foo/quxx",
"hello/world",
"foo/random/object",
"foo/random/another/object",
"xyz/hello",
"zzz/bca",
"some/very/nested/mp/object",
"foo/xyz",
} {
_, err := createMp(s3client, bucket, key)
if err != nil {
return err
}
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
Delimiter: getPtr("/"),
Prefix: getPtr("foo/"),
})
cancel()
if err != nil {
return err
}
expectedCps := []string{"foo/abc/", "foo/bar/", "foo/random/"}
if !comparePrefixes(expectedCps, out.CommonPrefixes) {
return fmt.Errorf("expected the common prefixes to be %v, instead got %v", expectedCps, out.CommonPrefixes)
}
return nil
})
}
func ListMultipartUploads_delimiter_no_matches(s *S3Conf) error {
testName := "ListMultipartUploads_delimiter_no_matches"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := make([]types.MultipartUpload, 0, 8)
for _, key := range []string{
"abc/something",
"foo/bar/baz",
"foo/quxx",
"hello/world",
"xyz/hello",
"zzz/bca",
"some/very/nested/mp/object",
"foo/xyz",
} {
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,
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
Delimiter: getPtr("delim"),
})
cancel()
if err != nil {
return err
}
sort.SliceStable(uploads, func(i, j int) bool {
return *uploads[i].Key < *uploads[j].Key
})
if !compareMultipartUploads(uploads, out.Uploads) {
return fmt.Errorf("expected the uploads to be %v, instead got %v", uploads, out.Uploads)
}
if len(out.CommonPrefixes) != 0 {
return fmt.Errorf("expected empty common prefixes, instead got %v", out.CommonPrefixes)
}
return nil
})
}
func ListMultipartUploads_with_checksums(s *S3Conf) error {
testName := "ListMultipartUploads_with_checksums"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := []types.MultipartUpload{}
for _, el := range []struct {
obj string
algo types.ChecksumAlgorithm
t types.ChecksumType
}{
{
obj: "obj-1",
algo: types.ChecksumAlgorithmCrc32,
t: types.ChecksumTypeComposite,
},
{
obj: "obj-2",
algo: types.ChecksumAlgorithmCrc32c,
t: types.ChecksumTypeFullObject,
},
{
obj: "obj-3",
algo: types.ChecksumAlgorithmSha1,
t: types.ChecksumTypeComposite,
},
{
obj: "obj-4",
algo: types.ChecksumAlgorithmSha256,
t: types.ChecksumTypeComposite,
},
{
obj: "obj-5",
algo: types.ChecksumAlgorithmCrc64nvme,
t: types.ChecksumTypeFullObject,
},
{
obj: "obj-6",
algo: types.ChecksumAlgorithmSha512,
t: types.ChecksumTypeComposite,
},
{
obj: "obj-7",
algo: types.ChecksumAlgorithmMd5,
t: types.ChecksumTypeComposite,
},
{
obj: "obj-8",
algo: types.ChecksumAlgorithmXxhash64,
t: types.ChecksumTypeComposite,
},
{
obj: "obj-9",
algo: types.ChecksumAlgorithmXxhash3,
t: types.ChecksumTypeComposite,
},
// obj-91 to preserve the lexicographical order
{
obj: "obj-91",
algo: types.ChecksumAlgorithmXxhash128,
t: types.ChecksumTypeComposite,
},
} {
key := el.obj
mp, err := createMp(s3client, bucket, key, withChecksum(el.algo), withChecksumType(el.t))
if err != nil {
return err
}
uploads = append(uploads, types.MultipartUpload{
Key: &key,
UploadId: mp.UploadId,
StorageClass: types.StorageClassStandard,
ChecksumAlgorithm: el.algo,
ChecksumType: el.t,
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if !compareMultipartUploads(res.Uploads, uploads) {
return fmt.Errorf("expected the final multipart uploads to be %v, instead got %v",
uploads, res.Uploads)
}
return nil
})
}
+46 -2
View File
@@ -205,6 +205,8 @@ func TestPutObject(ts *TestState) {
ts.Run(PutObject_with_metadata)
}
ts.Run(PutObject_success)
ts.Run(PutObject_sse_c_invalid_headers)
ts.Run(PutObject_unsupported_sse_not_implemented)
ts.Run(PutObject_default_content_type)
ts.Run(PutObject_overwrite_resets_attributes)
ts.Run(PutObject_overwrite_multipart_object)
@@ -238,6 +240,7 @@ func TestHeadObject(ts *TestState) {
ts.Run(HeadObject_ranged_with_checksum_mode)
}
ts.Run(HeadObject_success)
ts.Run(HeadObject_sse_c_invalid_headers)
ts.Run(HeadObject_overrides_success)
ts.Run(HeadObject_overrides_presign_success)
ts.Run(HeadObject_overrides_fail_public)
@@ -417,6 +420,7 @@ func TestCopyObject(ts *TestState) {
}
ts.Run(CopyObject_with_special_characters)
ts.Run(CopyObject_success)
ts.Run(CopyObject_sse_c_invalid_copy_source_headers)
ts.Run(CopyObject_cross_bucket_server_side_copy)
ts.Run(CopyObject_incorrect_source_bucket_expected_owner)
}
@@ -903,6 +907,28 @@ func TestNotImplementedActions(ts *TestState) {
ts.Run(GetObjectAcl_not_implemented)
}
// TestSSECNotImplemented is for backends that reject SSE-C rather than forward it.
func TestSSECNotImplemented(ts *TestState) {
ts.Run(PutObject_sse_c_not_implemented)
ts.Run(PostObject_sse_c_not_implemented)
ts.Run(GetObject_sse_c_not_implemented)
ts.Run(HeadObject_sse_c_not_implemented)
ts.Run(GetObjectAttributes_sse_c_not_implemented)
ts.Run(CopyObject_sse_c_not_implemented)
ts.Run(CreateMultipartUpload_sse_c_not_implemented)
ts.Run(UploadPart_sse_c_not_implemented)
ts.Run(UploadPartCopy_sse_c_not_implemented)
ts.Run(CompleteMultipartUpload_sse_c_not_implemented)
}
func TestSSECTransport(ts *TestState) {
if ts.conf.tlsStatus {
TestSSECNotImplemented(ts)
return
}
ts.Run(PutObject_sse_c_requires_tls)
}
func TestWORMProtection(ts *TestState) {
ts.Run(WORMProtection_bucket_object_lock_configuration_compliance_mode)
ts.Run(WORMProtection_bucket_object_lock_configuration_governance_mode)
@@ -1003,6 +1029,9 @@ func TestFullFlow(ts *TestState) {
if ts.conf.versioningEnabled {
TestVersioning(ts)
}
if ts.conf.azureTests {
TestSSECTransport(ts)
}
TestGatewayIAM(ts)
TestServer(ts)
}
@@ -1033,6 +1062,7 @@ func TestPosix(ts *TestState) {
if !ts.conf.versioningEnabled {
TestVersioningDisabled(ts)
}
TestSSECTransport(ts)
}
func TestScoutfs(ts *TestState) {
@@ -1134,6 +1164,7 @@ func TestScoutfs(ts *TestState) {
ts.Run(ObjectTagging_trailing_slash_counterpart)
ts.Run(ObjectLock_trailing_slash_counterpart)
ts.Run(DeleteObject_directory_not_empty)
TestSSECTransport(ts)
}
func TestGatewayIAM(ts *TestState) {
@@ -2162,7 +2193,6 @@ func TestRouter(ts *TestState) {
ts.Run(RouterObjectAnnotationNotImplemented)
ts.Run(RouterRenameObjectNotImplemented)
ts.Run(RouterObjectTorrentMethodNotAllowed)
ts.Run(RouterInvalidURLEscape)
}
func TestPostObject(ts *TestState) {
@@ -3540,6 +3570,21 @@ func GetIntTests() IntTests {
"PutObject_with_slashes": PutObject_with_slashes,
"PutObject_race_with_delete": PutObject_race_with_delete,
"CreateMultipartUpload_dir_obj": CreateMultipartUpload_dir_obj,
"PutObject_sse_c_not_implemented": PutObject_sse_c_not_implemented,
"PutObject_unsupported_sse_not_implemented": PutObject_unsupported_sse_not_implemented,
"PutObject_sse_c_requires_tls": PutObject_sse_c_requires_tls,
"PostObject_sse_c_not_implemented": PostObject_sse_c_not_implemented,
"GetObject_sse_c_not_implemented": GetObject_sse_c_not_implemented,
"HeadObject_sse_c_not_implemented": HeadObject_sse_c_not_implemented,
"GetObjectAttributes_sse_c_not_implemented": GetObjectAttributes_sse_c_not_implemented,
"CopyObject_sse_c_not_implemented": CopyObject_sse_c_not_implemented,
"CreateMultipartUpload_sse_c_not_implemented": CreateMultipartUpload_sse_c_not_implemented,
"UploadPart_sse_c_not_implemented": UploadPart_sse_c_not_implemented,
"UploadPartCopy_sse_c_not_implemented": UploadPartCopy_sse_c_not_implemented,
"CompleteMultipartUpload_sse_c_not_implemented": CompleteMultipartUpload_sse_c_not_implemented,
"PutObject_sse_c_invalid_headers": PutObject_sse_c_invalid_headers,
"HeadObject_sse_c_invalid_headers": HeadObject_sse_c_invalid_headers,
"CopyObject_sse_c_invalid_copy_source_headers": CopyObject_sse_c_invalid_copy_source_headers,
"IAM_user_access_denied": IAM_user_access_denied,
"IAM_userplus_access_denied": IAM_userplus_access_denied,
"IAM_userplus_CreateBucket": IAM_userplus_CreateBucket,
@@ -3750,7 +3795,6 @@ func GetIntTests() IntTests {
"RouterObjectAnnotationNotImplemented": RouterObjectAnnotationNotImplemented,
"RouterRenameObjectNotImplemented": RouterRenameObjectNotImplemented,
"RouterObjectTorrentMethodNotAllowed": RouterObjectTorrentMethodNotAllowed,
"RouterInvalidURLEscape": RouterInvalidURLEscape,
"UnsignedStreaminPayloadTrailer_malformed_trailer": UnsignedStreaminPayloadTrailer_malformed_trailer,
"UnsignedStreamingPayloadTrailer_aborted_connection": UnsignedStreamingPayloadTrailer_aborted_connection,
"UnsignedStreamingPayloadTrailer_missing_invalid_dec_content_length": UnsignedStreamingPayloadTrailer_missing_invalid_dec_content_length,
+231
View File
@@ -0,0 +1,231 @@
#!/usr/bin/env bats
# Copyright 2026 Versity Software
# This file is licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
load ./bats-support/load
load ./bats-assert/load
source ./tests/drivers/create_bucket/create_bucket_rest.sh
source ./tests/drivers/list_multipart_uploads/list_multipart_uploads_rest.sh
source ./tests/drivers/file.sh
source ./tests/setup.sh
# tags: curl,ListMultipartUploads,multipart
@test "REST - ListMultipartUploads - Initiator and Owner data shown" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2142"
fi
run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME"
assert_success
read -r bucket_name test_file <<< "$output"
run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id"
assert_success
upload_id=$output
local owner_id initiator_id
if [ "$DIRECT" == "true" ]; then
owner_id="$AWS_CANONICAL_ID"
initiator_id="arn:aws:iam::$DIRECT_AWS_USER_ID:user/$DIRECT_S3_ROOT_ACCOUNT_NAME"
else
owner_id="$AWS_ACCESS_KEY_ID"
initiator_id="$AWS_ACCESS_KEY_ID"
fi
run list_multipart_uploads_check_user_data "$bucket_name" "$upload_id" "$owner_id" "$initiator_id"
assert_success
}
# tags: curl,ListMultipartUploads,multipart,uploadId,next-key-marker,next-upload-id-marker
@test "REST - ListMultipartUploads - NextKeyMarker and NextUploadIdMarker set to last item" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2144"
fi
run setup_bucket_and_files_v3 "$BUCKET_ONE_NAME" 2
assert_success
read -r bucket_name test_file_one test_file_two <<< "$output"
local upload_id_one upload_id_two
run create_multipart_upload_rest "$bucket_name" "$test_file_one" "" "parse_upload_id"
assert_success
upload_id_one=$output
run create_multipart_upload_rest "$bucket_name" "$test_file_two" "" "parse_upload_id"
assert_success
upload_id_two=$output
local last_key last_upload_id
if [[ "$test_file_one" < "$test_file_two" ]]; then
last_key="$test_file_two"
last_upload_id="$upload_id_two"
else
last_key="$test_file_one"
last_upload_id="$upload_id_one"
fi
run list_multipart_uploads_check_next_values "$bucket_name" "$last_key" "$last_upload_id"
assert_success
}
# tags: curl,ListMultipartUploads,multipart,uploadId,key-marker
@test "REST - ListMultipartUploads - uploadId and key combo work" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2149"
fi
run setup_bucket_and_files_v3 "$BUCKET_ONE_NAME" 2
assert_success
read -r bucket_name test_file_one test_file_two <<< "$output"
local upload_id_one upload_id_two
run create_multipart_upload_rest "$bucket_name" "$test_file_one" "" "parse_upload_id"
assert_success
upload_id_one=$output
run create_multipart_upload_rest "$bucket_name" "$test_file_two" "" "parse_upload_id"
assert_success
upload_id_two=$output
local first_key first_upload_id second_key second_upload_id
if [[ "$test_file_one" < "$test_file_two" ]]; then
first_key="$test_file_one"
first_upload_id="$upload_id_one"
second_key="$test_file_two"
second_upload_id="$upload_id_two"
else
first_key="$test_file_two"
first_upload_id="$upload_id_two"
second_key="$test_file_one"
second_upload_id="$upload_id_one"
fi
run list_multipart_uploads_get_check_next_values "$bucket_name" "$second_key" "$second_upload_id" "$first_key" "$first_upload_id"
assert_success
}
# tags: curl,ListMultipartUploads,multipart,uploadId
@test "REST - ListMultipartUploads - uploadId without key should be ignored" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2150"
fi
run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME"
assert_success
read -r bucket_name test_file <<< "$output"
run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id"
assert_success
upload_id_one=$output
run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id"
assert_success
upload_id_two=$output
local first_upload_id second_upload_id
if [[ "$upload_id_one" < "$upload_id_two" ]]; then
first_upload_id="$upload_id_one"
second_upload_id="$upload_id_two"
else
first_upload_id="$upload_id_two"
second_upload_id="$upload_id_one"
fi
run list_multipart_uploads_check_no_upload_id_in_response "$bucket_name" "$second_upload_id" "$first_upload_id"
assert_success
}
# tags: curl,ListMultipartUploads,multipart,prefix,delimiter
@test "REST - ListMultipartUploads - prefix and delimiter" {
file_names=("a-b-1.txt" "a-b-2.txt" "a-b/c-1.txt" "a-b/c-2.txt" "a-b/d.txt" "a/c.txt")
local prefix="a-"
run create_test_files_and_folders "${file_names[@]}"
assert_success
run setup_bucket_v3 "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
for file_name in "${file_names[@]}"; do
run create_multipart_upload_rest "$bucket_name" "$file_name" "" ""
assert_success
done
run list_multipart_uploads_rest "$bucket_name"
assert_success
log 5 "uploads: $output"
run list_uploads_with_prefix_and_delimiter_check_results "$bucket_name" "$prefix" "/" "a-b/" "--" "a-b-1.txt" "a-b-2.txt"
assert_success
}
# tags: curl,ListMultipartUploads,multipart,encoding-type
@test "REST - ListMultipartUploads - encoding type" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2156"
fi
run setup_bucket_v3 "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
run create_test_files_with_prefix " "
assert_success
base_file_name="$output"
file_name="${base_file_name}(test)+"
log 5 "file name: '$file_name'"
run send_rest_go_command_callback "200" "parse_upload_id" "-method" "POST" "-query" "uploads" "-bucketName" "$bucket_name" "-objectKey" "$file_name"
assert_success
upload_id=$output
run list_multipart_uploads_check_encoding "$bucket_name" "&encoding-type=url" "${base_file_name/ /+}%28test%29%2B"
assert_success
run list_multipart_uploads_check_encoding "$bucket_name" "" "$file_name"
assert_success
}
# tags: curl,ListMultipartUploads,multipart,encoding-type,invalid-query
@test "REST - ListMultipartUploads - invalid encoding type" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2156"
fi
run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME"
assert_success
read -r bucket_name test_file <<< "$output"
run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id"
assert_success
upload_id_one=$output
local invalid_encoding="abc"
run send_rest_go_command_expect_error_with_arg_name_value "400" "InvalidArgument" "Invalid Encoding Method specified" \
"encoding-type" "$invalid_encoding" "-bucketName" "$bucket_name" "-query" "uploads&encoding-type=$invalid_encoding"
assert_success
}
# tags: curl,ListMultipartUploads,multipart,uploadId,invalid-query
@test "REST - ListMultipartUploads - uploads/uploadId combo" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2155"
fi
run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME"
assert_success
read -r bucket_name test_file <<< "$output"
run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id"
assert_success
upload_id_one=$output
run send_rest_go_command_expect_error "400" "InvalidArgument" "Conflicting query string parameters" "-query" "uploads&uploadId=$upload_id_one" "-bucketName" "$bucket_name"
assert_success
}