[#387] api: Add tests for middleware

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
poc/new-pilorama
Roman Loginov 2024-05-16 08:16:27 +03:00 committed by Alexey Vanin
parent f4d174e740
commit 21dbe3ea8e
2 changed files with 140 additions and 13 deletions

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"net/http" "net/http"
"testing" "testing"
@ -32,12 +33,22 @@ func (p *poolStatisticMock) Statistic() pool.Statistic {
} }
type centerMock struct { type centerMock struct {
t *testing.T t *testing.T
anon bool anon bool
attrs []object.Attribute noAuthHeader bool
isError bool
attrs []object.Attribute
} }
func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) { func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
if c.noAuthHeader {
return nil, middleware.ErrNoAuthorizationHeader
}
if c.isError {
return nil, fmt.Errorf("some error")
}
var token *bearer.Token var token *bearer.Token
if !c.anon { if !c.anon {
@ -86,14 +97,23 @@ func (r *middlewareSettingsMock) ACLEnabled() bool {
} }
type frostFSIDMock struct { type frostFSIDMock struct {
tags map[string]string tags map[string]string
validateError bool
userGroupsError bool
} }
func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error { func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error {
if f.validateError {
return fmt.Errorf("some error")
}
return nil return nil
} }
func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, error) { func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, error) {
if f.userGroupsError {
return nil, nil, fmt.Errorf("some error")
}
return []string{}, f.tags, nil return []string{}, f.tags, nil
} }
@ -105,17 +125,21 @@ func (m *xmlMock) NewXMLDecoder(r io.Reader) *xml.Decoder {
} }
type resourceTaggingMock struct { type resourceTaggingMock struct {
bucketTags map[string]string bucketTags map[string]string
objectTags map[string]string objectTags map[string]string
noSuchKey bool noSuchObjectKey bool
noSuchBucketKey bool
} }
func (m *resourceTaggingMock) GetBucketTagging(context.Context, *data.BucketInfo) (map[string]string, error) { func (m *resourceTaggingMock) GetBucketTagging(context.Context, *data.BucketInfo) (map[string]string, error) {
if m.noSuchBucketKey {
return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)
}
return m.bucketTags, nil return m.bucketTags, nil
} }
func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) { func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) {
if m.noSuchKey { if m.noSuchObjectKey {
return "", nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey) return "", nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)
} }
return "", m.objectTags, nil return "", m.objectTags, nil
@ -215,9 +239,13 @@ func (h *handlerMock) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
h.writeResponse(w, res) h.writeResponse(w, res)
} }
func (h *handlerMock) DeleteObjectHandler(http.ResponseWriter, *http.Request) { func (h *handlerMock) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
//TODO implement me res := &handlerResult{
panic("implement me") Method: middleware.DeleteObjectOperation,
ReqInfo: middleware.GetReqInfo(r.Context()),
}
h.writeResponse(w, res)
} }
func (h *handlerMock) GetBucketLocationHandler(http.ResponseWriter, *http.Request) { func (h *handlerMock) GetBucketLocationHandler(http.ResponseWriter, *http.Request) {

View File

@ -43,7 +43,15 @@ func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m.router.ServeHTTP(w, r) m.router.ServeHTTP(w, r)
} }
func prepareRouter(t *testing.T) *routerMock { type option func(*Config)
func frostFSIDValidation(flag bool) option {
return func(cfg *Config) {
cfg.FrostFSIDValidation = flag
}
}
func prepareRouter(t *testing.T, opts ...option) *routerMock {
middlewareSettings := &middlewareSettingsMock{} middlewareSettings := &middlewareSettingsMock{}
policyChecker := inmemory.NewInMemoryLocalOverrides() policyChecker := inmemory.NewInMemoryLocalOverrides()
@ -72,6 +80,11 @@ func prepareRouter(t *testing.T) *routerMock {
XMLDecoder: &xmlMock{}, XMLDecoder: &xmlMock{},
Tagging: &resourceTaggingMock{}, Tagging: &resourceTaggingMock{},
} }
for _, o := range opts {
o(&cfg)
}
return &routerMock{ return &routerMock{
t: t, t: t,
router: NewRouter(cfg), router: NewRouter(cfg),
@ -191,12 +204,25 @@ func TestPolicyChecker(t *testing.T) {
// check we can access 'bucket' in default namespace // check we can access 'bucket' in default namespace
putObject(chiRouter, ns1, bktName1, objName1, nil) putObject(chiRouter, ns1, bktName1, objName1, nil)
deleteObject(chiRouter, ns1, bktName1, objName1, nil)
// check we can access 'other-bucket' in custom namespace // check we can access 'other-bucket' in custom namespace
putObject(chiRouter, ns2, bktName2, objName2, nil) putObject(chiRouter, ns2, bktName2, objName2, nil)
deleteObject(chiRouter, ns2, bktName2, objName2, nil)
// check we cannot access 'bucket' in custom namespace // check we cannot access 'bucket' in custom namespace
putObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied) putObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied)
deleteObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied)
}
func TestPolicyCheckerError(t *testing.T) {
chiRouter := prepareRouter(t)
ns1, bktName1, objName1 := "", "bucket", "object"
putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apiErrors.ErrNoSuchBucket)
chiRouter = prepareRouter(t)
chiRouter.cfg.FrostfsID.(*frostFSIDMock).userGroupsError = true
putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apiErrors.ErrInternalError)
} }
func TestPolicyCheckerReqTypeDetermination(t *testing.T) { func TestPolicyCheckerReqTypeDetermination(t *testing.T) {
@ -507,6 +533,9 @@ func TestRequestTagsCheck(t *testing.T) {
tagging, err = xml.Marshal(data.Tagging{TagSet: []data.Tag{{Key: "key", Value: tagValue}}}) tagging, err = xml.Marshal(data.Tagging{TagSet: []data.Tag{{Key: "key", Value: tagValue}}})
require.NoError(t, err) require.NoError(t, err)
putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrAccessDenied) putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrAccessDenied)
tagging = nil
putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrMalformedXML)
}) })
t.Run("put object with tag", func(t *testing.T) { t.Run("put object with tag", func(t *testing.T) {
@ -588,7 +617,11 @@ func TestResourceTagsCheck(t *testing.T) {
listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrNoSuchBucket) listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrNoSuchBucket)
router.cfg.Tagging.(*resourceTaggingMock).noSuchKey = true router.cfg.Tagging.(*resourceTaggingMock).noSuchBucketKey = true
createBucket(router, ns, bktName)
getBucketErr(router, ns, bktName, apiErrors.ErrNoSuchKey)
router.cfg.Tagging.(*resourceTaggingMock).noSuchObjectKey = true
createBucket(router, ns, bktName) createBucket(router, ns, bktName)
getObjectErr(router, ns, bktName, objName, apiErrors.ErrNoSuchKey) getObjectErr(router, ns, bktName, objName, apiErrors.ErrNoSuchKey)
}) })
@ -739,6 +772,18 @@ func listBucketsBase(router *routerMock, namespace string) *httptest.ResponseRec
return w return w
} }
func getBucketErr(router *routerMock, namespace, bktName string, errCode apiErrors.ErrorCode) {
w := getBucketBase(router, namespace, bktName)
assertAPIError(router.t, w, errCode)
}
func getBucketBase(router *routerMock, namespace, bktName string) *httptest.ResponseRecorder {
w, r := httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/"+bktName, nil)
r.Header.Set(FrostfsNamespaceHeader, namespace)
router.ServeHTTP(w, r)
return w
}
func putObject(router *routerMock, namespace, bktName, objName string, tag *data.Tag) handlerResult { func putObject(router *routerMock, namespace, bktName, objName string, tag *data.Tag) handlerResult {
w := putObjectBase(router, namespace, bktName, objName, tag) w := putObjectBase(router, namespace, bktName, objName, tag)
resp := readResponse(router.t, w) resp := readResponse(router.t, w)
@ -764,6 +809,31 @@ func putObjectBase(router *routerMock, namespace, bktName, objName string, tag *
return w return w
} }
func deleteObject(router *routerMock, namespace, bktName, objName string, tag *data.Tag) handlerResult {
w := deleteObjectBase(router, namespace, bktName, objName, tag)
resp := readResponse(router.t, w)
require.Equal(router.t, s3middleware.DeleteObjectOperation, resp.Method)
return resp
}
func deleteObjectErr(router *routerMock, namespace, bktName, objName string, tag *data.Tag, errCode apiErrors.ErrorCode) {
w := deleteObjectBase(router, namespace, bktName, objName, tag)
assertAPIError(router.t, w, errCode)
}
func deleteObjectBase(router *routerMock, namespace, bktName, objName string, tag *data.Tag) *httptest.ResponseRecorder {
w, r := httptest.NewRecorder(), httptest.NewRequest(http.MethodDelete, "/"+bktName+"/"+objName, nil)
if tag != nil {
queries := url.Values{
tag.Key: []string{tag.Value},
}
r.Header.Set(AmzTagging, queries.Encode())
}
r.Header.Set(FrostfsNamespaceHeader, namespace)
router.ServeHTTP(w, r)
return w
}
func putBucketTagging(router *routerMock, namespace, bktName string, tagging []byte) handlerResult { func putBucketTagging(router *routerMock, namespace, bktName string, tagging []byte) handlerResult {
w := putBucketTaggingBase(router, namespace, bktName, tagging) w := putBucketTaggingBase(router, namespace, bktName, tagging)
resp := readResponse(router.t, w) resp := readResponse(router.t, w)
@ -873,6 +943,35 @@ func TestBillingMetrics(t *testing.T) {
require.Equal(t, "anon", dump.Requests[0].User) require.Equal(t, "anon", dump.Requests[0].User)
} }
func TestAuthenticate(t *testing.T) {
chiRouter := prepareRouter(t)
createBucket(chiRouter, "", "bkt-1")
chiRouter = prepareRouter(t)
chiRouter.cfg.Center.(*centerMock).noAuthHeader = true
createBucket(chiRouter, "", "bkt-2")
chiRouter = prepareRouter(t)
chiRouter.cfg.Center.(*centerMock).isError = true
createBucketErr(chiRouter, "", "bkt-3", nil, apiErrors.ErrAccessDenied)
}
func TestFrostFSIDValidation(t *testing.T) {
// successful frostFSID validation
chiRouter := prepareRouter(t, frostFSIDValidation(true))
createBucket(chiRouter, "", "bkt-1")
// anon request, skip frostFSID validation
chiRouter = prepareRouter(t, frostFSIDValidation(true))
chiRouter.cfg.Center.(*centerMock).anon = true
createBucket(chiRouter, "", "bkt-2")
// frostFSID validation failed
chiRouter = prepareRouter(t, frostFSIDValidation(true))
chiRouter.cfg.FrostfsID.(*frostFSIDMock).validateError = true
createBucketErr(chiRouter, "", "bkt-3", nil, apiErrors.ErrInternalError)
}
func readResponse(t *testing.T, w *httptest.ResponseRecorder) handlerResult { func readResponse(t *testing.T, w *httptest.ResponseRecorder) handlerResult {
var res handlerResult var res handlerResult