[#585] Add ListBuckets handler test
All checks were successful
/ DCO (pull_request) Successful in 2m46s
/ Vulncheck (pull_request) Successful in 3m28s
/ Builds (pull_request) Successful in 4m9s
/ Lint (pull_request) Successful in 4m48s
/ Tests (pull_request) Successful in 4m0s

Modify containers field in TestFrostFS in order to get determined order of containers between test runs

Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
This commit is contained in:
Nikita Zinkevich 2024-12-19 17:14:22 +03:00
parent 1e67f0dee4
commit 19c771bb97
Signed by: nzinkevich
GPG key ID: 748EA1D0B2E6420A
4 changed files with 252 additions and 22 deletions

View file

@ -7,6 +7,7 @@ import (
"encoding/xml"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
@ -297,10 +298,17 @@ type createBucketInfo struct {
Key *keys.PrivateKey
}
type bucketPrm struct {
bktName string
query url.Values
box *accessbox.Box
createParams createBucketParams
}
func createBucket(hc *handlerContext, bktName string) *createBucketInfo {
box, key := createAccessBox(hc.t)
w := createBucketBase(hc, bktName, box)
w := createBucketBase(hc, bucketPrm{bktName: bktName, box: box})
assertStatus(hc.t, w, http.StatusOK)
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
@ -314,13 +322,32 @@ func createBucket(hc *handlerContext, bktName string) *createBucketInfo {
}
func createBucketAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, code apierr.ErrorCode) {
w := createBucketBase(hc, bktName, box)
w := createBucketBase(hc, bucketPrm{bktName: bktName, box: box})
assertS3Error(hc.t, w, apierr.GetAPIError(code))
}
func createBucketBase(hc *handlerContext, bktName string, box *accessbox.Box) *httptest.ResponseRecorder {
w, r := prepareTestRequest(hc, bktName, "", nil)
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
func createBucketWithConstraint(hc *handlerContext, bktName, constraint string) *createBucketInfo {
box, key := createAccessBox(hc.t)
var prm createBucketParams
if constraint != "" {
prm.LocationConstraint = constraint
}
w := createBucketBase(hc, bucketPrm{bktName: bktName, box: box, createParams: prm})
assertStatus(hc.t, w, http.StatusOK)
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
require.NoError(hc.t, err)
return &createBucketInfo{
BktInfo: bktInfo,
Box: box,
Key: key,
}
}
func createBucketBase(hc *handlerContext, prm bucketPrm) *httptest.ResponseRecorder {
w, r := prepareTestFullRequest(hc, prm.bktName, "", nil, prm.createParams)
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: prm.box})
r = r.WithContext(ctx)
hc.Handler().CreateBucketHandler(w, r)
return w

View file

@ -0,0 +1,161 @@
package handler
import (
"encoding/xml"
"net/http"
"net/http/httptest"
"net/url"
"testing"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"github.com/stretchr/testify/require"
)
func TestHandler_ListBucketsHandler(t *testing.T) {
const defaultConstraint = "default"
region := "us-west-1"
hc := prepareHandlerContext(t)
hc.config.putLocationConstraint(region)
props := []Bucket{
{Name: "first"},
{Name: "regional", BucketRegion: "us-west-1"},
{Name: "third"},
}
for _, bkt := range props {
createBucketWithConstraint(hc, bkt.Name, bkt.BucketRegion)
}
for _, tt := range []struct {
title string
token string
prefix string
bucketRegion string
maxBuckets string
expectErr bool
expected []Bucket
expectedToken string
}{
{
title: "no params",
expected: []Bucket{
{Name: "first", BucketRegion: defaultConstraint},
{Name: "regional", BucketRegion: "us-west-1"},
{Name: "third", BucketRegion: defaultConstraint},
},
},
{
title: "negative max-buckets",
maxBuckets: "-1",
expected: []Bucket{},
expectErr: true,
},
{
title: "prefix",
prefix: "thi",
expected: []Bucket{{Name: "third", BucketRegion: defaultConstraint}},
},
{
title: "wrong prefix",
prefix: "sdh",
expected: []Bucket{},
},
{
title: "bucket region",
bucketRegion: region,
expected: []Bucket{{Name: "regional", BucketRegion: "us-west-1"}},
},
{
title: "default bucket region",
bucketRegion: defaultConstraint,
expected: []Bucket{
{Name: "first", BucketRegion: defaultConstraint},
{Name: "third", BucketRegion: defaultConstraint},
},
},
{
title: "wrong bucket region",
bucketRegion: "sj dfdlsj",
expected: []Bucket{},
},
} {
t.Run(tt.title, func(t *testing.T) {
if tt.expectErr {
listBucketsErr(hc, tt.prefix, tt.token, tt.bucketRegion, tt.maxBuckets, apierr.GetAPIError(apierr.ErrInvalidMaxKeys))
return
}
resp := listBuckets(hc, tt.prefix, tt.token, tt.bucketRegion, tt.maxBuckets)
require.Len(t, resp.Buckets.Buckets, len(tt.expected))
require.Equal(t, tt.prefix, resp.Prefix)
if len(resp.Buckets.Buckets) > 0 {
t.Log(resp.Buckets.Buckets[0].Name)
}
for i, bkt := range resp.Buckets.Buckets {
require.Equal(t, tt.expected[i].Name, bkt.Name)
require.Equal(t, tt.expected[i].BucketRegion, bkt.BucketRegion)
}
})
}
t.Run("pagination", func(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
resp := listBuckets(hc, "", "", "", "1")
require.Len(t, resp.Buckets.Buckets, 1)
require.NotEmpty(t, resp.ContinuationToken)
resp = listBuckets(hc, "", resp.ContinuationToken, "", "1")
require.Len(t, resp.Buckets.Buckets, 1)
require.NotEmpty(t, resp.ContinuationToken)
resp = listBuckets(hc, "", resp.ContinuationToken, "", "1")
require.Len(t, resp.Buckets.Buckets, 1)
require.Empty(t, resp.ContinuationToken)
})
t.Run("wrong continuation-token", func(t *testing.T) {
resp := listBuckets(hc, "", "CebuVwfRpdMqi9dvgV2SUNbrkfteGtudchKKhNabXUu9", "", "1")
require.Len(t, resp.Buckets.Buckets, 0)
require.Empty(t, resp.ContinuationToken)
})
})
}
func listBuckets(hc *handlerContext, prefix, token, bucketRegion, maxBuckets string) ListBucketsResponse {
query := url.Values{
middleware.QueryPrefix: []string{prefix},
middleware.QueryContinuationToken: []string{token},
middleware.QueryBucketRegion: []string{bucketRegion},
middleware.QueryMaxBuckets: []string{maxBuckets},
}
w := listBucketsBase(hc, bucketPrm{query: query})
assertStatus(hc.t, w, http.StatusOK)
var resp ListBucketsResponse
err := xml.NewDecoder(w.Body).Decode(&resp)
require.NoError(hc.t, err)
return resp
}
func listBucketsErr(hc *handlerContext, prefix, token, bucketRegion, maxBuckets string, err apierr.Error) {
query := url.Values{
middleware.QueryPrefix: []string{prefix},
middleware.QueryContinuationToken: []string{token},
middleware.QueryBucketRegion: []string{bucketRegion},
middleware.QueryMaxBuckets: []string{maxBuckets},
}
w := listBucketsBase(hc, bucketPrm{query: query})
assertS3Error(hc.t, w, err)
}
func listBucketsBase(hc *handlerContext, prm bucketPrm) *httptest.ResponseRecorder {
box, _ := createAccessBox(hc.t)
w, r := prepareTestFullRequest(hc, "", "", prm.query, nil)
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
r = r.WithContext(ctx)
hc.Handler().ListBucketsHandler(w, r)
return w
}

View file

@ -74,6 +74,7 @@ func (hc *handlerContextBase) Context() context.Context {
type configMock struct {
defaultPolicy netmap.PlacementPolicy
placementPolicies map[string]netmap.PlacementPolicy
copiesNumbers map[string][]uint32
defaultCopiesNumbers []uint32
bypassContentEncodingInChunks bool
@ -85,8 +86,9 @@ func (c *configMock) DefaultPlacementPolicy(_ string) netmap.PlacementPolicy {
return c.defaultPolicy
}
func (c *configMock) PlacementPolicy(_, _ string) (netmap.PlacementPolicy, bool) {
return netmap.PlacementPolicy{}, false
func (c *configMock) PlacementPolicy(_, constraint string) (netmap.PlacementPolicy, bool) {
policy, ok := c.placementPolicies[constraint]
return policy, ok
}
func (c *configMock) CopiesNumbers(_, locationConstraint string) ([]uint32, bool) {
@ -146,6 +148,10 @@ func (c *configMock) TLSTerminationHeader() string {
return c.tlsTerminationHeader
}
func (c *configMock) putLocationConstraint(constraint string) {
c.placementPolicies[constraint] = c.defaultPolicy
}
func prepareHandlerContext(t *testing.T) *handlerContext {
hc, err := prepareHandlerContextBase(layer.DefaultCachesConfigs(zap.NewExample()))
require.NoError(t, err)
@ -212,7 +218,8 @@ func prepareHandlerContextBase(cacheCfg *layer.CachesConfig) (*handlerContextBas
}
cfg := &configMock{
defaultPolicy: pp,
defaultPolicy: pp,
placementPolicies: make(map[string]netmap.PlacementPolicy),
}
h := &handler{
log: log,

View file

@ -74,11 +74,46 @@ func (k *FeatureSettingsMock) FormContainerZone(ns string) string {
var _ frostfs.FrostFS = (*TestFrostFS)(nil)
type containerKV struct {
key string
Container *container.Container
}
type containers []containerKV
func (c *containers) Get(key string) (*container.Container, bool) {
for _, info := range *c {
if info.key == key {
return info.Container, true
}
}
return nil, false
}
func (c *containers) Set(key string, value *container.Container) {
for _, info := range *c {
if info.key == key {
info.Container = value
return
}
}
*c = append(*c, containerKV{key: key, Container: value})
}
func (c *containers) Delete(key string) {
for i, info := range *c {
if info.key == key {
*c = append((*c)[:i], (*c)[i+1:]...)
}
}
}
type TestFrostFS struct {
objects map[string]*object.Object
objectErrors map[string]error
objectPutErrors map[string]error
containers map[string]*container.Container
containers containers
chains map[string][]chain.Chain
currentEpoch uint64
key *keys.PrivateKey
@ -90,7 +125,7 @@ func NewTestFrostFS(key *keys.PrivateKey) *TestFrostFS {
objects: make(map[string]*object.Object),
objectErrors: make(map[string]error),
objectPutErrors: make(map[string]error),
containers: make(map[string]*container.Container),
containers: make([]containerKV, 0, 10),
chains: make(map[string][]chain.Chain),
key: key,
}
@ -141,17 +176,17 @@ func (t *TestFrostFS) AddObject(key string, obj *object.Object) {
}
func (t *TestFrostFS) ContainerID(name string) (cid.ID, error) {
for id, cnr := range t.containers {
if container.Name(*cnr) == name {
for _, cnr := range t.containers {
if container.Name(*cnr.Container) == name {
var cnrID cid.ID
return cnrID, cnrID.DecodeString(id)
return cnrID, cnrID.DecodeString(cnr.key)
}
}
return cid.ID{}, fmt.Errorf("not found")
}
func (t *TestFrostFS) SetContainer(cnrID cid.ID, cnr *container.Container) {
t.containers[cnrID.EncodeToString()] = cnr
t.containers.Set(cnrID.EncodeToString(), cnr)
}
func (t *TestFrostFS) CreateContainer(_ context.Context, prm frostfs.PrmContainerCreate) (*frostfs.ContainerCreateResult, error) {
@ -186,22 +221,22 @@ func (t *TestFrostFS) CreateContainer(_ context.Context, prm frostfs.PrmContaine
var id cid.ID
id.SetSHA256(sha256.Sum256(b))
t.containers[id.EncodeToString()] = &cnr
t.containers.Set(id.EncodeToString(), &cnr)
t.chains[id.EncodeToString()] = []chain.Chain{}
return &frostfs.ContainerCreateResult{ContainerID: id}, nil
}
func (t *TestFrostFS) DeleteContainer(_ context.Context, cnrID cid.ID, _ *session.Container) error {
delete(t.containers, cnrID.EncodeToString())
t.containers.Delete(cnrID.EncodeToString())
return nil
}
func (t *TestFrostFS) Container(_ context.Context, prm frostfs.PrmContainer) (*container.Container, error) {
for k, v := range t.containers {
if k == prm.ContainerID.EncodeToString() {
return v, nil
for _, v := range t.containers {
if v.key == prm.ContainerID.EncodeToString() {
return v.Container, nil
}
}
@ -210,9 +245,9 @@ func (t *TestFrostFS) Container(_ context.Context, prm frostfs.PrmContainer) (*c
func (t *TestFrostFS) UserContainers(context.Context, frostfs.PrmUserContainers) ([]cid.ID, error) {
var res []cid.ID
for k := range t.containers {
for _, info := range t.containers {
var idCnr cid.ID
if err := idCnr.DecodeString(k); err != nil {
if err := idCnr.DecodeString(info.key); err != nil {
return nil, err
}
res = append(res, idCnr)
@ -531,7 +566,7 @@ func (t *TestFrostFS) AddContainerPolicyChain(_ context.Context, prm frostfs.Prm
}
func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID) bool {
cnr, ok := t.containers[cnrID.EncodeToString()]
cnr, ok := t.containers.Get(cnrID.EncodeToString())
if !ok {
return false
}