forked from TrueCloudLab/frostfs-s3-gw
[#192] Add base lifecycle tests
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
410db92aa2
commit
5bfb8fd291
3 changed files with 186 additions and 14 deletions
|
@ -9,15 +9,15 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
Rule struct {
|
Rule struct {
|
||||||
AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload" json:"AbortIncompleteMultipartUpload"`
|
AbortIncompleteMultipartUpload *AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload" json:"AbortIncompleteMultipartUpload"`
|
||||||
Expiration Expiration `xml:"Expiration" json:"Expiration"`
|
Expiration *Expiration `xml:"Expiration" json:"Expiration"`
|
||||||
Filter LifecycleRuleFilter `xml:"Filter" json:"Filter"`
|
Filter *LifecycleRuleFilter `xml:"Filter" json:"Filter"`
|
||||||
ID string `xml:"ID" json:"ID"`
|
ID string `xml:"ID" json:"ID"`
|
||||||
NoncurrentVersionExpiration NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration" json:"NoncurrentVersionExpiration"`
|
NoncurrentVersionExpiration *NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration" json:"NoncurrentVersionExpiration"`
|
||||||
NoncurrentVersionTransitions []NoncurrentVersionTransition `xml:"NoncurrentVersionTransition" json:"NoncurrentVersionTransition"`
|
NoncurrentVersionTransitions []NoncurrentVersionTransition `xml:"NoncurrentVersionTransition" json:"NoncurrentVersionTransition"`
|
||||||
Prefix string `xml:"Prefix" json:"Prefix"`
|
Prefix string `xml:"Prefix" json:"Prefix"`
|
||||||
Status string `xml:"Status" json:"Status"`
|
Status string `xml:"Status" json:"Status"`
|
||||||
Transitions []Transition `xml:"Transition" json:"Transition"`
|
Transitions []Transition `xml:"Transition" json:"Transition"`
|
||||||
}
|
}
|
||||||
|
|
||||||
AbortIncompleteMultipartUpload struct {
|
AbortIncompleteMultipartUpload struct {
|
||||||
|
@ -31,11 +31,11 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
LifecycleRuleFilter struct {
|
LifecycleRuleFilter struct {
|
||||||
And LifecycleRuleAndOperator `xml:"And" json:"And"`
|
And *LifecycleRuleAndOperator `xml:"And" json:"And"`
|
||||||
ObjectSizeGreaterThan int64 `xml:"ObjectSizeGreaterThan" json:"ObjectSizeGreaterThan"`
|
ObjectSizeGreaterThan int64 `xml:"ObjectSizeGreaterThan" json:"ObjectSizeGreaterThan"`
|
||||||
ObjectSizeLessThan int64 `xml:"ObjectSizeLessThan" json:"ObjectSizeLessThan"`
|
ObjectSizeLessThan int64 `xml:"ObjectSizeLessThan" json:"ObjectSizeLessThan"`
|
||||||
Prefix string `xml:"Prefix" json:"Prefix"`
|
Prefix string `xml:"Prefix" json:"Prefix"`
|
||||||
Tag Tag `xml:"Tag" json:"Tag"`
|
Tag *Tag `xml:"Tag" json:"Tag"`
|
||||||
}
|
}
|
||||||
|
|
||||||
LifecycleRuleAndOperator struct {
|
LifecycleRuleAndOperator struct {
|
||||||
|
|
|
@ -123,7 +123,31 @@ func checkLifecycleConfiguration(conf *data.LifecycleConfiguration) error {
|
||||||
if rule.Status != enabledValue && rule.Status != disabledValue {
|
if rule.Status != enabledValue && rule.Status != disabledValue {
|
||||||
return apiErrors.GetAPIError(apiErrors.ErrMalformedXML)
|
return apiErrors.GetAPIError(apiErrors.ErrMalformedXML)
|
||||||
}
|
}
|
||||||
|
if rule.Filter != nil {
|
||||||
|
if rule.Filter.ObjectSizeGreaterThan < 0 || rule.Filter.ObjectSizeLessThan < 0 {
|
||||||
|
return apiErrors.GetAPIError(apiErrors.ErrMalformedXML)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filterContainsOneOption(rule.Filter) {
|
||||||
|
return apiErrors.GetAPIError(apiErrors.ErrMalformedXML)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterContainsOneOption(filter *data.LifecycleRuleFilter) bool {
|
||||||
|
exactlyOneOption := 0
|
||||||
|
if filter.Prefix != "" {
|
||||||
|
exactlyOneOption++
|
||||||
|
}
|
||||||
|
if filter.And != nil {
|
||||||
|
exactlyOneOption++
|
||||||
|
}
|
||||||
|
if filter.Tag != nil {
|
||||||
|
exactlyOneOption++
|
||||||
|
}
|
||||||
|
|
||||||
|
return exactlyOneOption == 1
|
||||||
|
}
|
||||||
|
|
148
api/handler/lifecycle_test.go
Normal file
148
api/handler/lifecycle_test.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-s3-gw/api/data"
|
||||||
|
apiErrors "github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckLifecycleConfiguration(t *testing.T) {
|
||||||
|
numRules := 1001
|
||||||
|
rules := make([]data.Rule, numRules)
|
||||||
|
for i := 0; i < numRules; i++ {
|
||||||
|
rules[i] = data.Rule{ID: strconv.Itoa(i), Status: disabledValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
configuration *data.LifecycleConfiguration
|
||||||
|
noError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic",
|
||||||
|
configuration: &data.LifecycleConfiguration{Rules: []data.Rule{{
|
||||||
|
ID: "Some ID",
|
||||||
|
Status: "Disabled",
|
||||||
|
}}},
|
||||||
|
noError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid status",
|
||||||
|
configuration: &data.LifecycleConfiguration{Rules: []data.Rule{{
|
||||||
|
ID: "Some ID",
|
||||||
|
Status: "",
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero rules",
|
||||||
|
configuration: &data.LifecycleConfiguration{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "more than max rules",
|
||||||
|
configuration: &data.LifecycleConfiguration{Rules: rules},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid empty filter",
|
||||||
|
configuration: &data.LifecycleConfiguration{Rules: []data.Rule{{
|
||||||
|
Status: enabledValue,
|
||||||
|
Filter: &data.LifecycleRuleFilter{},
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid filter not exactly one option",
|
||||||
|
configuration: &data.LifecycleConfiguration{Rules: []data.Rule{{
|
||||||
|
Status: enabledValue,
|
||||||
|
Filter: &data.LifecycleRuleFilter{
|
||||||
|
Prefix: "prefix",
|
||||||
|
Tag: &data.Tag{},
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid filter greater obj size",
|
||||||
|
configuration: &data.LifecycleConfiguration{Rules: []data.Rule{{
|
||||||
|
Status: enabledValue,
|
||||||
|
Filter: &data.LifecycleRuleFilter{
|
||||||
|
ObjectSizeGreaterThan: -1,
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid filter less obj size",
|
||||||
|
configuration: &data.LifecycleConfiguration{Rules: []data.Rule{{
|
||||||
|
Status: enabledValue,
|
||||||
|
Filter: &data.LifecycleRuleFilter{
|
||||||
|
ObjectSizeLessThan: -1,
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := checkLifecycleConfiguration(tc.configuration)
|
||||||
|
if tc.noError {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBucketLifecycleConfiguration(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName := "bucket-for-lifecycle"
|
||||||
|
createTestBucket(ctx, t, hc, bktName)
|
||||||
|
|
||||||
|
w, r := prepareTestRequest(t, bktName, "", nil)
|
||||||
|
hc.Handler().GetBucketLifecycleHandler(w, r)
|
||||||
|
assertS3Error(t, w, apiErrors.GetAPIError(apiErrors.ErrNoSuchLifecycleConfiguration))
|
||||||
|
|
||||||
|
lifecycleConf := &data.LifecycleConfiguration{
|
||||||
|
XMLName: xmlName("LifecycleConfiguration"),
|
||||||
|
Rules: []data.Rule{
|
||||||
|
{
|
||||||
|
AbortIncompleteMultipartUpload: &data.AbortIncompleteMultipartUpload{},
|
||||||
|
ID: "Test",
|
||||||
|
Status: "Disabled",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
w, r = prepareTestRequest(t, bktName, "", lifecycleConf)
|
||||||
|
hc.Handler().PutBucketLifecycleHandler(w, r)
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
w, r = prepareTestRequest(t, bktName, "", nil)
|
||||||
|
hc.Handler().GetBucketLifecycleHandler(w, r)
|
||||||
|
assertXMLEqual(t, w, lifecycleConf, &data.LifecycleConfiguration{})
|
||||||
|
|
||||||
|
w, r = prepareTestRequest(t, bktName, "", lifecycleConf)
|
||||||
|
hc.Handler().DeleteBucketLifecycleHandler(w, r)
|
||||||
|
require.Equal(t, http.StatusNoContent, w.Code)
|
||||||
|
|
||||||
|
// make sure deleting is idempotent operation
|
||||||
|
w, r = prepareTestRequest(t, bktName, "", lifecycleConf)
|
||||||
|
hc.Handler().DeleteBucketLifecycleHandler(w, r)
|
||||||
|
require.Equal(t, http.StatusNoContent, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertXMLEqual(t *testing.T, w *httptest.ResponseRecorder, expected, actual interface{}) {
|
||||||
|
err := xml.NewDecoder(w.Result().Body).Decode(actual)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func xmlName(local string) xml.Name {
|
||||||
|
return xml.Name{
|
||||||
|
Space: "http://s3.amazonaws.com/doc/2006-03-01/",
|
||||||
|
Local: local,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue