frostfs-node/services/public/object/head_test.go

596 lines
15 KiB
Go
Raw Normal View History

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())
})
})
})
}