forked from TrueCloudLab/frostfs-node
dadfd90dcd
Initial public review release v0.10.0
595 lines
15 KiB
Go
595 lines
15 KiB
Go
package object
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/object"
|
|
"github.com/nspcc-dev/neofs-api-go/service"
|
|
"github.com/nspcc-dev/neofs-node/internal"
|
|
"github.com/nspcc-dev/neofs-node/lib/transformer"
|
|
"github.com/nspcc-dev/neofs-node/lib/transport"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type (
|
|
// Entity for mocking interfaces.
|
|
// Implementation of any interface intercepts arguments via f (if not nil).
|
|
// If err is not nil, it returns as it is. Otherwise, casted to needed type res returns w/o error.
|
|
testHeadEntity struct {
|
|
// Set of interfaces which entity must implement, but some methods from those does not call.
|
|
transformer.ObjectRestorer
|
|
|
|
// Argument interceptor. Used for ascertain of correct parameter passage between components.
|
|
f func(...interface{})
|
|
// Mocked result of any interface.
|
|
res interface{}
|
|
// Mocked error of any interface.
|
|
err error
|
|
}
|
|
)
|
|
|
|
var (
|
|
_ ancestralObjectsReceiver = (*testHeadEntity)(nil)
|
|
_ objectChildrenLister = (*testHeadEntity)(nil)
|
|
_ objectReceiver = (*testHeadEntity)(nil)
|
|
_ requestHandler = (*testHeadEntity)(nil)
|
|
_ operationExecutor = (*testHeadEntity)(nil)
|
|
_ objectRewinder = (*testHeadEntity)(nil)
|
|
_ transformer.ObjectRestorer = (*testHeadEntity)(nil)
|
|
_ responsePreparer = (*testHeadEntity)(nil)
|
|
)
|
|
|
|
func (s *testHeadEntity) prepareResponse(_ context.Context, req serviceRequest, resp serviceResponse) error {
|
|
if s.f != nil {
|
|
s.f(req, resp)
|
|
}
|
|
return s.err
|
|
}
|
|
|
|
func (s *testHeadEntity) getFromChildren(ctx context.Context, addr Address, ids []ID, h bool) (*objectData, error) {
|
|
if s.f != nil {
|
|
s.f(addr, ids, h, ctx)
|
|
}
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
return s.res.(*objectData), nil
|
|
}
|
|
|
|
func (s *testHeadEntity) Restore(_ context.Context, objs ...Object) ([]Object, error) {
|
|
if s.f != nil {
|
|
s.f(objs)
|
|
}
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
return s.res.([]Object), nil
|
|
}
|
|
|
|
func (s *testHeadEntity) rewind(ctx context.Context, objs ...Object) (*Object, error) {
|
|
if s.f != nil {
|
|
s.f(objs)
|
|
}
|
|
return s.res.(*Object), s.err
|
|
}
|
|
|
|
func (s *testHeadEntity) executeOperation(_ context.Context, i transport.MetaInfo, h responseItemHandler) error {
|
|
if s.f != nil {
|
|
s.f(i, h)
|
|
}
|
|
return s.err
|
|
}
|
|
|
|
func (s *testHeadEntity) children(ctx context.Context, addr Address) []ID {
|
|
if s.f != nil {
|
|
s.f(addr, ctx)
|
|
}
|
|
return s.res.([]ID)
|
|
}
|
|
|
|
func (s *testHeadEntity) getObject(_ context.Context, p ...transport.GetInfo) (*objectData, error) {
|
|
if s.f != nil {
|
|
s.f(p)
|
|
}
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
return s.res.(*objectData), nil
|
|
}
|
|
|
|
func (s *testHeadEntity) handleRequest(_ context.Context, p handleRequestParams) (interface{}, error) {
|
|
if s.f != nil {
|
|
s.f(p)
|
|
}
|
|
return s.res, s.err
|
|
}
|
|
|
|
func Test_transportRequest_HeadInfo(t *testing.T) {
|
|
t.Run("address", func(t *testing.T) {
|
|
t.Run("valid request", func(t *testing.T) {
|
|
addr := testObjectAddress(t)
|
|
|
|
reqs := []transportRequest{
|
|
{serviceRequest: &object.HeadRequest{Address: addr}},
|
|
{serviceRequest: &object.GetRequest{Address: addr}},
|
|
{serviceRequest: &GetRangeRequest{Address: addr}},
|
|
{serviceRequest: &object.GetRangeHashRequest{Address: addr}},
|
|
{serviceRequest: &object.DeleteRequest{Address: addr}},
|
|
}
|
|
|
|
for i := range reqs {
|
|
require.Equal(t, addr, reqs[i].GetAddress())
|
|
}
|
|
})
|
|
|
|
t.Run("unknown request", func(t *testing.T) {
|
|
req := new(object.SearchRequest)
|
|
|
|
r := &transportRequest{
|
|
serviceRequest: req,
|
|
}
|
|
|
|
require.PanicsWithValue(t, fmt.Sprintf(pmWrongRequestType, req), func() {
|
|
_ = r.GetAddress()
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("full headers", func(t *testing.T) {
|
|
r := &transportRequest{
|
|
serviceRequest: &object.HeadRequest{
|
|
FullHeaders: true,
|
|
},
|
|
}
|
|
|
|
require.True(t, r.GetFullHeaders())
|
|
})
|
|
|
|
t.Run("raw", func(t *testing.T) {
|
|
hReq := new(object.HeadRequest)
|
|
hReq.SetRaw(true)
|
|
|
|
r := &transportRequest{
|
|
serviceRequest: hReq,
|
|
}
|
|
require.True(t, r.Raw())
|
|
|
|
hReq.SetRaw(false)
|
|
require.False(t, r.Raw())
|
|
})
|
|
}
|
|
|
|
func Test_rawHeadInfo(t *testing.T) {
|
|
t.Run("address", func(t *testing.T) {
|
|
addr := testObjectAddress(t)
|
|
|
|
r := newRawHeadInfo()
|
|
r.setAddress(addr)
|
|
|
|
require.Equal(t, addr, r.GetAddress())
|
|
})
|
|
|
|
t.Run("full headers", func(t *testing.T) {
|
|
r := newRawHeadInfo()
|
|
r.setFullHeaders(true)
|
|
|
|
require.True(t, r.GetFullHeaders())
|
|
})
|
|
}
|
|
|
|
func Test_coreObjAccum(t *testing.T) {
|
|
t.Run("new", func(t *testing.T) {
|
|
s := newObjectAccumulator()
|
|
v := s.(*coreObjAccum)
|
|
require.Nil(t, v.obj)
|
|
require.NotNil(t, v.Once)
|
|
})
|
|
|
|
t.Run("handle/object", func(t *testing.T) {
|
|
obj1 := new(Object)
|
|
|
|
s := newObjectAccumulator()
|
|
|
|
// add first object
|
|
s.handleItem(obj1)
|
|
|
|
// ascertain tha object was added
|
|
require.Equal(t, obj1, s.object())
|
|
|
|
obj2 := new(Object)
|
|
|
|
// add second object
|
|
s.handleItem(obj2)
|
|
|
|
// ascertain that second object was ignored
|
|
require.Equal(t, obj1, s.object())
|
|
})
|
|
}
|
|
|
|
func Test_objectService_Head(t *testing.T) {
|
|
ctx := context.TODO()
|
|
|
|
t.Run("request handler error", func(t *testing.T) {
|
|
// create custom error for test
|
|
rhErr := internal.Error("test error for request handler")
|
|
|
|
// create custom request for test
|
|
req := new(object.HeadRequest)
|
|
|
|
s := &objectService{
|
|
statusCalculator: newStatusCalculator(),
|
|
}
|
|
|
|
s.requestHandler = &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct request handler params", func(t *testing.T) {
|
|
p := items[0].(handleRequestParams)
|
|
require.Equal(t, s, p.executor)
|
|
require.Equal(t, req, p.request)
|
|
})
|
|
},
|
|
err: rhErr, // force requestHandler to return rhErr
|
|
}
|
|
|
|
res, err := s.Head(ctx, req)
|
|
require.EqualError(t, err, rhErr.Error())
|
|
require.Nil(t, res)
|
|
})
|
|
|
|
t.Run("correct resulst", func(t *testing.T) {
|
|
obj := &objectData{Object: new(Object)}
|
|
|
|
resp := &object.HeadResponse{Object: obj.Object}
|
|
|
|
req := new(object.HeadRequest)
|
|
|
|
s := &objectService{
|
|
requestHandler: &testHeadEntity{
|
|
res: obj, // force request handler to return obj
|
|
},
|
|
respPreparer: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
require.Equal(t, req, items[0])
|
|
require.Equal(t, makeHeadResponse(obj.Object), items[1])
|
|
},
|
|
res: resp,
|
|
},
|
|
|
|
statusCalculator: newStatusCalculator(),
|
|
}
|
|
|
|
res, err := s.Head(ctx, new(object.HeadRequest))
|
|
require.NoError(t, err)
|
|
require.Equal(t, resp, res)
|
|
})
|
|
}
|
|
|
|
func Test_coreHeadReceiver_head(t *testing.T) {
|
|
ctx := context.TODO()
|
|
|
|
t.Run("raw handling", func(t *testing.T) {
|
|
// create custom head info for test
|
|
hInfo := newRawHeadInfo()
|
|
hInfo.setRaw(true)
|
|
|
|
// create custom error for test
|
|
srErr := internal.Error("test error for straight object receiver")
|
|
|
|
s := &coreObjectReceiver{
|
|
straightObjRecv: &testHeadEntity{
|
|
err: srErr, // force straightObjectReceiver to return srErr
|
|
},
|
|
}
|
|
|
|
_, err := s.getObject(ctx, hInfo)
|
|
// ascertain that straightObjectReceiver result returns in raw case as expected
|
|
require.EqualError(t, err, srErr.Error())
|
|
})
|
|
|
|
t.Run("straight receive of non-linking object", func(t *testing.T) {
|
|
// create custom head info for test
|
|
hInfo := newRawHeadInfo()
|
|
|
|
// create object w/o children for test
|
|
obj := &objectData{Object: new(Object)}
|
|
|
|
s := &coreObjectReceiver{
|
|
straightObjRecv: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct straight receiver params", func(t *testing.T) {
|
|
require.Equal(t, []transport.GetInfo{hInfo}, items[0])
|
|
})
|
|
},
|
|
res: obj,
|
|
},
|
|
}
|
|
|
|
res, err := s.getObject(ctx, hInfo)
|
|
require.NoError(t, err)
|
|
require.Equal(t, obj, res)
|
|
})
|
|
|
|
t.Run("linking object/non-assembly", func(t *testing.T) {
|
|
// create custom head info for test
|
|
hInfo := newRawHeadInfo()
|
|
|
|
// create object w/ children for test
|
|
obj := &objectData{
|
|
Object: &Object{Headers: []Header{{Value: &object.Header_Link{Link: &object.Link{Type: object.Link_Child}}}}},
|
|
}
|
|
|
|
s := &coreObjectReceiver{
|
|
straightObjRecv: &testHeadEntity{
|
|
res: obj, // force straightObjectReceiver to return obj
|
|
},
|
|
ancestralRecv: nil, // make component to be non-assembly
|
|
}
|
|
|
|
res, err := s.getObject(ctx, hInfo)
|
|
require.EqualError(t, err, errNonAssembly.Error())
|
|
require.Nil(t, res)
|
|
})
|
|
|
|
t.Run("children search failure", func(t *testing.T) {
|
|
addr := testObjectAddress(t)
|
|
|
|
hInfo := newRawHeadInfo()
|
|
hInfo.setAddress(addr)
|
|
hInfo.setSessionToken(new(service.Token))
|
|
|
|
s := &coreObjectReceiver{
|
|
straightObjRecv: &testHeadEntity{
|
|
err: internal.Error(""), // force straightObjectReceiver to return non-empty error
|
|
},
|
|
childLister: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct child lister params", func(t *testing.T) {
|
|
require.Equal(t, addr, items[0])
|
|
require.Equal(t,
|
|
hInfo.GetSessionToken(),
|
|
items[1].(context.Context).Value(transformer.PublicSessionToken),
|
|
)
|
|
})
|
|
},
|
|
res: make([]ID, 0), // force objectChildren lister to return empty list
|
|
},
|
|
ancestralRecv: new(testHeadEntity),
|
|
}
|
|
|
|
res, err := s.getObject(ctx, hInfo)
|
|
require.EqualError(t, err, childrenNotFound.Error())
|
|
require.Nil(t, res)
|
|
})
|
|
|
|
t.Run("correct result", func(t *testing.T) {
|
|
var (
|
|
childCount = 5
|
|
rErr = internal.Error("test error for rewinding receiver")
|
|
children = make([]ID, 0, childCount)
|
|
)
|
|
|
|
for i := 0; i < childCount; i++ {
|
|
id := testObjectAddress(t).ObjectID
|
|
children = append(children, id)
|
|
}
|
|
|
|
// create custom head info
|
|
hInfo := newRawHeadInfo()
|
|
hInfo.setTTL(5)
|
|
hInfo.setTimeout(3 * time.Second)
|
|
hInfo.setAddress(testObjectAddress(t))
|
|
hInfo.setSessionToken(new(service.Token))
|
|
|
|
t.Run("error/children from straight receiver", func(t *testing.T) {
|
|
obj := &objectData{Object: new(Object)}
|
|
|
|
for i := range children {
|
|
// add child reference to object
|
|
obj.Headers = append(obj.Headers, Header{
|
|
Value: &object.Header_Link{Link: &object.Link{Type: object.Link_Child, ID: children[i]}},
|
|
})
|
|
}
|
|
|
|
s := &coreObjectReceiver{
|
|
straightObjRecv: &testHeadEntity{
|
|
res: obj, // force straight receiver to return obj
|
|
},
|
|
ancestralRecv: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct rewinding receiver", func(t *testing.T) {
|
|
require.Equal(t, hInfo.GetAddress(), items[0])
|
|
require.Equal(t, children, items[1])
|
|
require.True(t, items[2].(bool))
|
|
require.Equal(t,
|
|
hInfo.GetSessionToken(),
|
|
items[3].(context.Context).Value(transformer.PublicSessionToken),
|
|
)
|
|
})
|
|
},
|
|
err: rErr, // force rewinding receiver to return rErr
|
|
},
|
|
log: zap.L(),
|
|
}
|
|
|
|
res, err := s.getObject(ctx, hInfo)
|
|
require.EqualError(t, err, errIncompleteOperation.Error())
|
|
require.Nil(t, res)
|
|
})
|
|
|
|
t.Run("success/children from child lister", func(t *testing.T) {
|
|
obj := &objectData{Object: new(Object)}
|
|
|
|
s := &coreObjectReceiver{
|
|
straightObjRecv: &testHeadEntity{
|
|
err: internal.Error(""), // force straight receiver to return non-nil error
|
|
},
|
|
ancestralRecv: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct rewinding receiver", func(t *testing.T) {
|
|
require.Equal(t, hInfo.GetAddress(), items[0])
|
|
require.Equal(t, children, items[1])
|
|
require.True(t, items[2].(bool))
|
|
})
|
|
},
|
|
res: obj, // force rewinding receiver to return obj
|
|
},
|
|
childLister: &testHeadEntity{
|
|
res: children, // force objectChildrenLister to return particular list
|
|
},
|
|
}
|
|
|
|
res, err := s.getObject(ctx, hInfo)
|
|
require.NoError(t, err, rErr.Error())
|
|
require.Equal(t, obj, res)
|
|
})
|
|
})
|
|
}
|
|
|
|
func Test_straightHeadReceiver_head(t *testing.T) {
|
|
ctx := context.TODO()
|
|
|
|
hInfo := newRawHeadInfo()
|
|
hInfo.setFullHeaders(true)
|
|
|
|
t.Run("executor error", func(t *testing.T) {
|
|
exErr := internal.Error("test error for operation executor")
|
|
|
|
s := &straightObjectReceiver{
|
|
executor: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct operation executor params", func(t *testing.T) {
|
|
require.Equal(t, hInfo, items[0])
|
|
_ = items[1].(objectAccumulator)
|
|
})
|
|
},
|
|
err: exErr, // force operationExecutor to return exErr
|
|
},
|
|
}
|
|
|
|
_, err := s.getObject(ctx, hInfo)
|
|
require.EqualError(t, err, exErr.Error())
|
|
|
|
hInfo = newRawHeadInfo()
|
|
hInfo.setFullHeaders(true)
|
|
|
|
_, err = s.getObject(ctx, hInfo)
|
|
require.EqualError(t, err, exErr.Error())
|
|
})
|
|
|
|
t.Run("correct result", func(t *testing.T) {
|
|
obj := &objectData{Object: new(Object), payload: new(emptyReader)}
|
|
|
|
s := &straightObjectReceiver{
|
|
executor: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
items[1].(objectAccumulator).handleItem(obj.Object)
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := s.getObject(ctx, hInfo)
|
|
require.NoError(t, err)
|
|
require.Equal(t, obj, res)
|
|
})
|
|
}
|
|
|
|
func Test_coreObjectRewinder_rewind(t *testing.T) {
|
|
ctx := context.TODO()
|
|
|
|
t.Run("transformer failure", func(t *testing.T) {
|
|
tErr := internal.Error("test error for object transformer")
|
|
objs := []Object{*new(Object), *new(Object)}
|
|
|
|
s := &coreObjectRewinder{
|
|
transformer: &testHeadEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct transformer params", func(t *testing.T) {
|
|
require.Equal(t, objs, items[0])
|
|
})
|
|
},
|
|
err: tErr, // force transformer to return tErr
|
|
},
|
|
}
|
|
|
|
res, err := s.rewind(ctx, objs...)
|
|
require.EqualError(t, err, tErr.Error())
|
|
require.Empty(t, res)
|
|
})
|
|
|
|
t.Run("correct result", func(t *testing.T) {
|
|
objs := []Object{
|
|
{SystemHeader: SystemHeader{ID: testObjectAddress(t).ObjectID}},
|
|
{SystemHeader: SystemHeader{ID: testObjectAddress(t).ObjectID}},
|
|
}
|
|
|
|
s := &coreObjectRewinder{
|
|
transformer: &testHeadEntity{
|
|
res: objs, // force transformer to return objs
|
|
},
|
|
}
|
|
|
|
res, err := s.rewind(ctx, objs...)
|
|
require.NoError(t, err)
|
|
require.Equal(t, &objs[0], res)
|
|
})
|
|
}
|
|
|
|
func Test_coreObjectReceiver_sendingRequest(t *testing.T) {
|
|
t.Run("non-assembly", func(t *testing.T) {
|
|
src := &transportRequest{serviceRequest: new(object.GetRequest)}
|
|
// ascertain that request not changed if node is non-assembled
|
|
require.Equal(t, src, new(coreObjectReceiver).sendingRequest(src))
|
|
})
|
|
|
|
t.Run("assembly", func(t *testing.T) {
|
|
s := &coreObjectReceiver{ancestralRecv: new(testHeadEntity)}
|
|
|
|
t.Run("raw request", func(t *testing.T) {
|
|
src := newRawGetInfo()
|
|
src.setRaw(true)
|
|
// ascertain that request not changed if request is raw
|
|
require.Equal(t, src, s.sendingRequest(src))
|
|
})
|
|
|
|
t.Run("non-raw request", func(t *testing.T) {
|
|
getInfo := *newRawGetInfo()
|
|
getInfo.setTTL(uint32(5))
|
|
getInfo.setTimeout(3 * time.Second)
|
|
getInfo.setAddress(testObjectAddress(t))
|
|
getInfo.setRaw(false)
|
|
getInfo.setSessionToken(new(service.Token))
|
|
|
|
t.Run("get", func(t *testing.T) {
|
|
res := s.sendingRequest(getInfo)
|
|
require.Equal(t, getInfo.GetTimeout(), res.GetTimeout())
|
|
require.Equal(t, getInfo.GetAddress(), res.GetAddress())
|
|
require.Equal(t, getInfo.GetTTL(), res.GetTTL())
|
|
require.Equal(t, getInfo.GetSessionToken(), res.GetSessionToken())
|
|
require.True(t, res.GetRaw())
|
|
|
|
t.Run("zero ttl", func(t *testing.T) {
|
|
res := s.sendingRequest(newRawGetInfo())
|
|
require.Equal(t, uint32(service.NonForwardingTTL), res.GetTTL())
|
|
})
|
|
})
|
|
|
|
t.Run("head", func(t *testing.T) {
|
|
hInfo := newRawHeadInfo()
|
|
hInfo.setGetInfo(getInfo)
|
|
hInfo.setFullHeaders(false)
|
|
|
|
res := s.sendingRequest(hInfo)
|
|
require.Equal(t, getInfo.GetTimeout(), res.GetTimeout())
|
|
require.Equal(t, getInfo.GetAddress(), res.GetAddress())
|
|
require.Equal(t, getInfo.GetTTL(), res.GetTTL())
|
|
require.Equal(t, getInfo.GetSessionToken(), res.GetSessionToken())
|
|
require.True(t, res.GetRaw())
|
|
require.True(t, res.(transport.HeadInfo).GetFullHeaders())
|
|
})
|
|
})
|
|
})
|
|
}
|