Denis Kirillov
14ef9ff091
All checks were successful
/ Vulncheck (pull_request) Successful in 1m40s
/ Builds (1.19) (pull_request) Successful in 2m54s
/ Builds (1.20) (pull_request) Successful in 2m39s
/ DCO (pull_request) Successful in 3m48s
/ Lint (pull_request) Successful in 3m32s
/ Tests (1.19) (pull_request) Successful in 2m38s
/ Tests (1.20) (pull_request) Successful in 2m55s
To be able to handle cases and return appropriate http status code when object missed in storage but gate cache contains its metadata we need write code after init object reader. So we separate init reader from actual reading. Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
226 lines
7.1 KiB
Go
226 lines
7.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"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"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
s3errors "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: errors.GetAPIError(errors.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: errors.GetAPIError(errors.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: errors.GetAPIError(errors.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: errors.GetAPIError(errors.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: errors.GetAPIError(errors.ErrPreconditionFailed),
|
|
},
|
|
{
|
|
name: "IfNoneMatch false, IfModifiedSince true",
|
|
info: newInfo(etag, today),
|
|
args: &conditionalArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday},
|
|
expected: errors.GetAPIError(errors.ErrNotModified),
|
|
},
|
|
{
|
|
name: "IfNoneMatch true, IfModifiedSince false",
|
|
info: newInfo(etag, yesterday),
|
|
args: &conditionalArgs{IfNoneMatch: etag2, IfModifiedSince: &today},
|
|
expected: errors.GetAPIError(errors.ErrNotModified),
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actual := checkPreconditions(tc.info, tc.args)
|
|
require.Equal(t, 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.t, 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(), s3errors.ErrNoSuchVersion)
|
|
getObjectAssertS3Error(hc, bktName, objName, emptyVersion, s3errors.ErrNoSuchKey)
|
|
}
|
|
|
|
func putObjectContent(hc *handlerContext, bktName, objName, content string) {
|
|
body := bytes.NewReader([]byte(content))
|
|
w, r := prepareTestPayloadRequest(hc, bktName, objName, body)
|
|
hc.Handler().PutObjectHandler(w, r)
|
|
assertStatus(hc.t, w, http.StatusOK)
|
|
}
|
|
|
|
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 getObjectAssertS3Error(hc *handlerContext, bktName, objName, version string, code apiErrors.ErrorCode) {
|
|
w := getObjectBase(hc, bktName, objName, version)
|
|
assertS3Error(hc.t, w, apiErrors.GetAPIError(code))
|
|
}
|
|
|
|
func getObjectBase(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
|
|
}
|