frostfs-s3-gw/api/handler/get_test.go

273 lines
8.9 KiB
Go
Raw Normal View History

package handler
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"github.com/stretchr/testify/require"
)
func TestFetchRangeHeader(t *testing.T) {
for _, tc := range []struct {
header string
expected *layer.RangeParams
fullSize uint64
err bool
}{
{header: "bytes=0-256", expected: &layer.RangeParams{Start: 0, End: 256}, fullSize: 257, err: false},
{header: "bytes=0-0", expected: &layer.RangeParams{Start: 0, End: 0}, fullSize: 1, err: false},
{header: "bytes=0-256", expected: &layer.RangeParams{Start: 0, End: 255}, fullSize: 256, err: false},
{header: "bytes=0-", expected: &layer.RangeParams{Start: 0, End: 99}, fullSize: 100, err: false},
{header: "bytes=-10", expected: &layer.RangeParams{Start: 90, End: 99}, fullSize: 100, err: false},
{header: "", err: false},
{header: "bytes=-1-256", err: true},
{header: "bytes=256-0", err: true},
{header: "bytes=string-0", err: true},
{header: "bytes=0-string", err: true},
{header: "bytes:0-256", err: true},
{header: "bytes:-", err: true},
{header: "bytes=0-0", fullSize: 0, err: true},
{header: "bytes=10-20", fullSize: 5, err: true},
} {
h := make(http.Header)
h.Add("Range", tc.header)
params, err := fetchRangeHeader(h, tc.fullSize)
if tc.err {
require.Error(t, err)
continue
}
require.NoError(t, err)
require.Equal(t, tc.expected, params)
}
}
func newInfo(etag string, created time.Time) *data.ObjectInfo {
return &data.ObjectInfo{
HashSum: etag,
Created: created,
}
}
func TestPreconditions(t *testing.T) {
today := time.Now()
yesterday := today.Add(-24 * time.Hour)
etag := "etag"
etag2 := "etag2"
for _, tc := range []struct {
name string
info *data.ObjectInfo
args *conditionalArgs
expected error
}{
{
name: "no conditions",
info: new(data.ObjectInfo),
args: new(conditionalArgs),
expected: nil,
},
{
name: "IfMatch true",
info: newInfo(etag, today),
args: &conditionalArgs{IfMatch: etag},
expected: nil,
},
{
name: "IfMatch false",
info: newInfo(etag, today),
args: &conditionalArgs{IfMatch: etag2},
expected: apierr.GetAPIError(apierr.ErrPreconditionFailed)},
{
name: "IfNoneMatch true",
info: newInfo(etag, today),
args: &conditionalArgs{IfNoneMatch: etag2},
expected: nil},
{
name: "IfNoneMatch false",
info: newInfo(etag, today),
args: &conditionalArgs{IfNoneMatch: etag},
expected: apierr.GetAPIError(apierr.ErrNotModified)},
{
name: "IfModifiedSince true",
info: newInfo(etag, today),
args: &conditionalArgs{IfModifiedSince: &yesterday},
expected: nil},
{
name: "IfModifiedSince false",
info: newInfo(etag, yesterday),
args: &conditionalArgs{IfModifiedSince: &today},
expected: apierr.GetAPIError(apierr.ErrNotModified)},
{
name: "IfUnmodifiedSince true",
info: newInfo(etag, yesterday),
args: &conditionalArgs{IfUnmodifiedSince: &today},
expected: nil},
{
name: "IfUnmodifiedSince false",
info: newInfo(etag, today),
args: &conditionalArgs{IfUnmodifiedSince: &yesterday},
expected: apierr.GetAPIError(apierr.ErrPreconditionFailed)},
{
name: "IfMatch true, IfUnmodifiedSince false",
info: newInfo(etag, today),
args: &conditionalArgs{IfMatch: etag, IfUnmodifiedSince: &yesterday},
expected: nil,
},
{
name: "IfMatch false, IfUnmodifiedSince true",
info: newInfo(etag, yesterday),
args: &conditionalArgs{IfMatch: etag2, IfUnmodifiedSince: &today},
expected: apierr.GetAPIError(apierr.ErrPreconditionFailed),
},
{
name: "IfNoneMatch false, IfModifiedSince true",
info: newInfo(etag, today),
args: &conditionalArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday},
expected: apierr.GetAPIError(apierr.ErrNotModified),
},
{
name: "IfNoneMatch true, IfModifiedSince false",
info: newInfo(etag, yesterday),
args: &conditionalArgs{IfNoneMatch: etag2, IfModifiedSince: &today},
expected: apierr.GetAPIError(apierr.ErrNotModified),
},
} {
t.Run(tc.name, func(t *testing.T) {
actual := checkPreconditions(tc.info, tc.args, false)
if tc.expected == nil {
require.NoError(t, actual)
} else {
require.True(t, errors.Is(actual, tc.expected), tc.expected, actual)
}
})
}
}
func TestGetRange(t *testing.T) {
tc := prepareHandlerContext(t)
bktName, objName := "bucket-for-range", "object-to-range"
createTestBucket(tc, bktName)
content := "123456789abcdef"
putObjectContent(tc, bktName, objName, content)
full := getObjectRange(t, tc, bktName, objName, 0, len(content)-1)
require.Equal(t, content, string(full))
beginning := getObjectRange(t, tc, bktName, objName, 0, 3)
require.Equal(t, content[:4], string(beginning))
middle := getObjectRange(t, tc, bktName, objName, 5, 10)
require.Equal(t, "6789ab", string(middle))
end := getObjectRange(t, tc, bktName, objName, 10, 15)
require.Equal(t, "bcdef", string(end))
}
func TestGetObject(t *testing.T) {
hc := prepareHandlerContext(t)
bktName, objName := "bucket", "obj"
bktInfo, objInfo := createVersionedBucketAndObject(hc.t, hc, bktName, objName)
putObject(hc, bktName, objName)
checkFound(hc.t, hc, bktName, objName, objInfo.VersionID())
checkFound(hc.t, hc, bktName, objName, emptyVersion)
addr := getAddressOfLastVersion(hc, bktInfo, objName)
hc.tp.SetObjectError(addr, &apistatus.ObjectNotFound{})
hc.tp.SetObjectError(objInfo.Address(), &apistatus.ObjectNotFound{})
getObjectAssertS3Error(hc, bktName, objName, objInfo.VersionID(), apierr.ErrNoSuchVersion)
getObjectAssertS3Error(hc, bktName, objName, emptyVersion, apierr.ErrNoSuchKey)
}
func TestGetObjectEnabledMD5(t *testing.T) {
hc := prepareHandlerContext(t)
bktName, objName := "bucket", "obj"
_, objInfo := createBucketAndObject(hc, bktName, objName)
_, headers := getObject(hc, bktName, objName)
require.Equal(t, data.Quote(objInfo.HashSum), headers.Get(api.ETag))
hc.config.md5Enabled = true
_, headers = getObject(hc, bktName, objName)
require.Equal(t, data.Quote(objInfo.MD5Sum), headers.Get(api.ETag))
}
func TestGetObjectNotModifiedHeaders(t *testing.T) {
hc := prepareHandlerContextWithMinCache(t)
bktName, objName, metadataHeader := "bucket", "obj", api.MetadataPrefix+"header"
createVersionedBucket(hc, bktName)
header := putObjectWithHeaders(hc, bktName, objName, map[string]string{api.CacheControl: "value", metadataHeader: "value"})
etag, versionID := header.Get(api.ETag), header.Get(api.AmzVersionID)
require.NotEmpty(t, etag)
require.NotEmpty(t, versionID)
putObjectTagging(t, hc, bktName, objName, map[string]string{"key": "value"})
w := getObjectWithHeaders(hc, bktName, objName, map[string]string{api.IfNoneMatch: etag})
require.Equal(t, http.StatusNotModified, w.Code)
require.Equal(t, "1", w.Header().Get(api.AmzTaggingCount))
require.Equal(t, etag, w.Header().Get(api.ETag))
require.NotEmpty(t, w.Header().Get(api.LastModified))
require.Equal(t, versionID, w.Header().Get(api.AmzVersionID))
require.Equal(t, "value", w.Header().Get(api.CacheControl))
require.Equal(t, []string{"value"}, w.Header()[metadataHeader])
}
func putObjectContent(hc *handlerContext, bktName, objName, content string) http.Header {
body := bytes.NewReader([]byte(content))
w, r := prepareTestPayloadRequest(hc, bktName, objName, body)
hc.Handler().PutObjectHandler(w, r)
assertStatus(hc.t, w, http.StatusOK)
return w.Result().Header
}
func getObjectRange(t *testing.T, tc *handlerContext, bktName, objName string, start, end int) []byte {
w, r := prepareTestRequest(tc, bktName, objName, nil)
r.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
tc.Handler().GetObjectHandler(w, r)
assertStatus(t, w, http.StatusPartialContent)
content, err := io.ReadAll(w.Result().Body)
require.NoError(t, err)
return content
}
func getObjectVersion(tc *handlerContext, bktName, objName, version string) []byte {
w := getObjectBaseResponse(tc, bktName, objName, version)
assertStatus(tc.t, w, http.StatusOK)
content, err := io.ReadAll(w.Result().Body)
require.NoError(tc.t, err)
return content
}
func getObjectAssertS3Error(hc *handlerContext, bktName, objName, version string, code apierr.ErrorCode) {
w := getObjectBaseResponse(hc, bktName, objName, version)
assertS3Error(hc.t, w, apierr.GetAPIError(code))
}
func getObjectBaseResponse(hc *handlerContext, bktName, objName, version string) *httptest.ResponseRecorder {
query := make(url.Values)
query.Add(api.QueryVersionID, version)
w, r := prepareTestFullRequest(hc, bktName, objName, query, nil)
hc.Handler().GetObjectHandler(w, r)
return w
}