diff --git a/api/handler/handlers_test.go b/api/handler/handlers_test.go index 1dc40d3..bf7b41d 100644 --- a/api/handler/handlers_test.go +++ b/api/handler/handlers_test.go @@ -4,10 +4,13 @@ import ( "bytes" "context" "encoding/xml" + "io" "math/rand" "net/http" "net/http/httptest" + "strconv" "testing" + "time" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-s3-gw/api" @@ -17,6 +20,7 @@ import ( "github.com/nspcc-dev/neofs-s3-gw/api/resolver" "github.com/nspcc-dev/neofs-s3-gw/internal/neofstest" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -111,12 +115,16 @@ func createTestObject(ctx context.Context, t *testing.T, h *handlerContext, bktI _, err := rand.Read(content) require.NoError(t, err) + header := map[string]string{ + object.AttributeTimestamp: strconv.FormatInt(time.Now().UTC().Unix(), 10), + } + _, err = h.Layer().PutObject(ctx, &layer.PutObjectParams{ BktInfo: bktInfo, Object: objName, Size: int64(len(content)), Reader: bytes.NewReader(content), - Header: make(map[string]string), + Header: header, }) require.NoError(t, err) } @@ -133,3 +141,11 @@ func prepareTestRequest(t *testing.T, bktName, objName string, body interface{}) return w, r } + +func assertStatus(t *testing.T, w *httptest.ResponseRecorder, status int) { + if w.Code != status { + resp, err := io.ReadAll(w.Result().Body) + require.NoError(t, err) + require.Failf(t, string(resp), "assert status fail, expected: %d, actual: %d", status, w.Code) + } +} diff --git a/api/handler/head_test.go b/api/handler/head_test.go new file mode 100644 index 0000000..f6beb27 --- /dev/null +++ b/api/handler/head_test.go @@ -0,0 +1,85 @@ +package handler + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/nspcc-dev/neofs-s3-gw/api" + "github.com/stretchr/testify/require" +) + +func TestConditionalHead(t *testing.T) { + ctx := context.Background() + tc := prepareHandlerContext(t) + + bktName := "bucket-for-conditional" + createTestBucket(ctx, t, tc, bktName) + bktInfo, err := tc.Layer().GetBucketInfo(ctx, bktName) + require.NoError(t, err) + + objName := "object" + createTestObject(ctx, t, tc, bktInfo, objName) + + w, r := prepareTestRequest(t, bktName, objName, nil) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusOK) + + etag := w.Result().Header.Get(api.ETag) + lastModified := w.Result().Header.Get(api.LastModified) + _ = lastModified + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfMatch, etag) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusOK) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfMatch, "etag") + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusPreconditionFailed) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfUnmodifiedSince, time.Now().UTC().Format(http.TimeFormat)) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusOK) + + var zeroTime time.Time + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfUnmodifiedSince, zeroTime.UTC().Format(http.TimeFormat)) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusPreconditionFailed) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfMatch, etag) + r.Header.Set(api.IfUnmodifiedSince, zeroTime.UTC().Format(http.TimeFormat)) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusOK) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfNoneMatch, etag) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusNotModified) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfNoneMatch, "etag") + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusOK) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfModifiedSince, zeroTime.UTC().Format(http.TimeFormat)) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusOK) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfModifiedSince, time.Now().Add(time.Minute).UTC().Format(http.TimeFormat)) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusNotModified) + + w, r = prepareTestRequest(t, bktName, objName, nil) + r.Header.Set(api.IfNoneMatch, etag) + r.Header.Set(api.IfModifiedSince, zeroTime.UTC().Format(http.TimeFormat)) + tc.Handler().HeadObjectHandler(w, r) + assertStatus(t, w, http.StatusNotModified) +} diff --git a/internal/neofstest/neofs_mock.go b/internal/neofstest/neofs_mock.go index 77fcf51..1ab7cd2 100644 --- a/internal/neofstest/neofs_mock.go +++ b/internal/neofstest/neofs_mock.go @@ -13,6 +13,7 @@ import ( objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-s3-gw/api/layer/neofs" + "github.com/nspcc-dev/neofs-sdk-go/checksum" "github.com/nspcc-dev/neofs-sdk-go/container" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" @@ -229,6 +230,9 @@ func (t *TestNeoFS) CreateObject(_ context.Context, prm neofs.PrmObjectCreate) ( } obj.SetPayload(all) obj.SetPayloadSize(uint64(len(all))) + var hash checksum.Checksum + checksum.Calculate(&hash, checksum.SHA256, all) + obj.SetPayloadChecksum(hash) } cnrID, _ := obj.ContainerID()