forked from TrueCloudLab/frostfs-node
dadfd90dcd
Initial public review release v0.10.0
449 lines
11 KiB
Go
449 lines
11 KiB
Go
package object
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/object"
|
|
"github.com/nspcc-dev/neofs-api-go/service"
|
|
"github.com/nspcc-dev/neofs-api-go/session"
|
|
"github.com/nspcc-dev/neofs-node/internal"
|
|
"github.com/nspcc-dev/neofs-node/lib/rand"
|
|
"github.com/nspcc-dev/neofs-node/lib/transformer"
|
|
"github.com/nspcc-dev/neofs-node/lib/transport"
|
|
"github.com/pkg/errors"
|
|
"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.
|
|
testDeleteEntity struct {
|
|
// Set of interfaces which testDeleteEntity must implement, but some methods from those does not call.
|
|
session.PrivateTokenStore
|
|
|
|
// 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 (
|
|
_ EpochReceiver = (*testDeleteEntity)(nil)
|
|
_ objectStorer = (*testDeleteEntity)(nil)
|
|
_ tombstoneCreator = (*testDeleteEntity)(nil)
|
|
_ objectChildrenLister = (*testDeleteEntity)(nil)
|
|
_ objectRemover = (*testDeleteEntity)(nil)
|
|
_ requestHandler = (*testDeleteEntity)(nil)
|
|
_ deletePreparer = (*testDeleteEntity)(nil)
|
|
_ responsePreparer = (*testDeleteEntity)(nil)
|
|
)
|
|
|
|
func (s *testDeleteEntity) verify(context.Context, *session.Token, *Object) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *testDeleteEntity) Fetch(id session.PrivateTokenKey) (session.PrivateToken, error) {
|
|
if s.f != nil {
|
|
s.f(id)
|
|
}
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
return s.res.(session.PrivateToken), nil
|
|
}
|
|
|
|
func (s *testDeleteEntity) prepareResponse(_ context.Context, req serviceRequest, resp serviceResponse) error {
|
|
if s.f != nil {
|
|
s.f(req, resp)
|
|
}
|
|
return s.err
|
|
}
|
|
|
|
func (s *testDeleteEntity) Epoch() uint64 { return s.res.(uint64) }
|
|
|
|
func (s *testDeleteEntity) putObject(_ context.Context, p transport.PutInfo) (*Address, error) {
|
|
if s.f != nil {
|
|
s.f(p)
|
|
}
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
return s.res.(*Address), nil
|
|
}
|
|
|
|
func (s *testDeleteEntity) createTombstone(_ context.Context, p deleteInfo) *Object {
|
|
if s.f != nil {
|
|
s.f(p)
|
|
}
|
|
return s.res.(*Object)
|
|
}
|
|
|
|
func (s *testDeleteEntity) children(ctx context.Context, addr Address) []ID {
|
|
if s.f != nil {
|
|
s.f(addr, ctx)
|
|
}
|
|
return s.res.([]ID)
|
|
}
|
|
|
|
func (s *testDeleteEntity) delete(ctx context.Context, p deleteInfo) error {
|
|
if s.f != nil {
|
|
s.f(p, ctx)
|
|
}
|
|
return s.err
|
|
}
|
|
|
|
func (s *testDeleteEntity) prepare(_ context.Context, p deleteInfo) ([]deleteInfo, error) {
|
|
if s.f != nil {
|
|
s.f(p)
|
|
}
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
return s.res.([]deleteInfo), nil
|
|
}
|
|
|
|
func (s *testDeleteEntity) handleRequest(_ context.Context, p handleRequestParams) (interface{}, error) {
|
|
if s.f != nil {
|
|
s.f(p)
|
|
}
|
|
return s.res, s.err
|
|
}
|
|
|
|
func Test_objectService_Delete(t *testing.T) {
|
|
ctx := context.TODO()
|
|
req := &object.DeleteRequest{Address: testObjectAddress(t)}
|
|
|
|
t.Run("handler error", func(t *testing.T) {
|
|
rhErr := internal.Error("test error for request handler")
|
|
|
|
s := &objectService{
|
|
statusCalculator: newStatusCalculator(),
|
|
}
|
|
|
|
s.requestHandler = &testDeleteEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct request handler params", func(t *testing.T) {
|
|
p := items[0].(handleRequestParams)
|
|
require.Equal(t, req, p.request)
|
|
require.Equal(t, s, p.executor)
|
|
})
|
|
},
|
|
err: rhErr, // force requestHandler to return rhErr
|
|
}
|
|
|
|
res, err := s.Delete(ctx, req)
|
|
// ascertain that error returns as expected
|
|
require.EqualError(t, err, rhErr.Error())
|
|
require.Nil(t, res)
|
|
})
|
|
|
|
t.Run("correct result", func(t *testing.T) {
|
|
s := objectService{
|
|
requestHandler: new(testDeleteEntity),
|
|
respPreparer: &testDeleteEntity{res: new(object.DeleteResponse)},
|
|
|
|
statusCalculator: newStatusCalculator(),
|
|
}
|
|
|
|
res, err := s.Delete(ctx, req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, new(object.DeleteResponse), res)
|
|
})
|
|
}
|
|
|
|
func Test_coreObjRemover_delete(t *testing.T) {
|
|
ctx := context.TODO()
|
|
pToken, err := session.NewPrivateToken(0)
|
|
require.NoError(t, err)
|
|
|
|
addr := testObjectAddress(t)
|
|
|
|
token := new(service.Token)
|
|
token.SetAddress(addr)
|
|
|
|
req := newRawDeleteInfo()
|
|
req.setAddress(addr)
|
|
req.setSessionToken(token)
|
|
|
|
t.Run("nil token", func(t *testing.T) {
|
|
s := new(coreObjRemover)
|
|
|
|
req := newRawDeleteInfo()
|
|
require.Nil(t, req.GetSessionToken())
|
|
|
|
require.EqualError(t, s.delete(ctx, req), errNilToken.Error())
|
|
})
|
|
|
|
t.Run("prepare error", func(t *testing.T) {
|
|
dpErr := internal.Error("test error for delete preparer")
|
|
|
|
dp := &testDeleteEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct delete preparer params", func(t *testing.T) {
|
|
require.Equal(t, req, items[0])
|
|
})
|
|
},
|
|
err: dpErr, // force deletePreparer to return dpErr
|
|
}
|
|
|
|
s := &coreObjRemover{
|
|
delPrep: dp,
|
|
tokenStore: &testDeleteEntity{res: pToken},
|
|
mErr: map[error]struct{}{
|
|
dpErr: {},
|
|
},
|
|
log: zap.L(),
|
|
}
|
|
|
|
// ascertain that error returns as expected
|
|
require.EqualError(t, s.delete(ctx, req), dpErr.Error())
|
|
|
|
dp.err = internal.Error("some other error")
|
|
|
|
// ascertain that error returns as expected
|
|
require.EqualError(t, s.delete(ctx, req), errDeletePrepare.Error())
|
|
})
|
|
|
|
t.Run("straight remover error", func(t *testing.T) {
|
|
dInfo := newRawDeleteInfo()
|
|
dInfo.setAddress(addr)
|
|
dInfo.setSessionToken(token)
|
|
|
|
list := []deleteInfo{
|
|
dInfo,
|
|
}
|
|
|
|
srErr := internal.Error("test error for straight remover")
|
|
|
|
s := &coreObjRemover{
|
|
delPrep: &testDeleteEntity{
|
|
res: list, // force deletePreparer to return list
|
|
},
|
|
straightRem: &testDeleteEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct straight remover params", func(t *testing.T) {
|
|
require.Equal(t, list[0], items[0])
|
|
|
|
ctx := items[1].(context.Context)
|
|
|
|
require.Equal(t,
|
|
dInfo.GetSessionToken(),
|
|
ctx.Value(transformer.PublicSessionToken),
|
|
)
|
|
|
|
require.Equal(t,
|
|
pToken,
|
|
ctx.Value(transformer.PrivateSessionToken),
|
|
)
|
|
})
|
|
},
|
|
err: srErr, // force objectRemover to return srErr
|
|
},
|
|
tokenStore: &testDeleteEntity{res: pToken},
|
|
}
|
|
|
|
// ascertain that error returns as expected
|
|
require.EqualError(t, s.delete(ctx, req), errors.Wrapf(srErr, emRemovePart, 1, 1).Error())
|
|
})
|
|
|
|
t.Run("success", func(t *testing.T) {
|
|
dInfo := newRawDeleteInfo()
|
|
dInfo.setAddress(addr)
|
|
dInfo.setSessionToken(token)
|
|
|
|
list := []deleteInfo{
|
|
dInfo,
|
|
}
|
|
|
|
s := &coreObjRemover{
|
|
delPrep: &testDeleteEntity{
|
|
res: list, // force deletePreparer to return list
|
|
},
|
|
straightRem: &testDeleteEntity{
|
|
err: nil, // force objectRemover to return empty error
|
|
},
|
|
tokenStore: &testDeleteEntity{res: pToken},
|
|
}
|
|
|
|
// ascertain that nil error returns
|
|
require.NoError(t, s.delete(ctx, req))
|
|
})
|
|
}
|
|
|
|
func Test_coreDelPreparer_prepare(t *testing.T) {
|
|
var (
|
|
ctx = context.TODO()
|
|
ownerID = OwnerID{1, 2, 3}
|
|
addr = testObjectAddress(t)
|
|
timeout = 5 * time.Second
|
|
token = new(service.Token)
|
|
childCount = 10
|
|
children = make([]ID, 0, childCount)
|
|
)
|
|
|
|
req := newRawDeleteInfo()
|
|
req.setAddress(addr)
|
|
req.setSessionToken(token)
|
|
req.setOwnerID(ownerID)
|
|
|
|
token.SetID(session.TokenID{1, 2, 3})
|
|
|
|
for i := 0; i < childCount; i++ {
|
|
children = append(children, testObjectAddress(t).ObjectID)
|
|
}
|
|
|
|
s := &coreDelPreparer{
|
|
timeout: timeout,
|
|
childLister: &testDeleteEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct children lister params", func(t *testing.T) {
|
|
require.Equal(t, addr, items[0])
|
|
require.Equal(t,
|
|
token,
|
|
items[1].(context.Context).Value(transformer.PublicSessionToken),
|
|
)
|
|
})
|
|
},
|
|
res: children,
|
|
},
|
|
}
|
|
|
|
res, err := s.prepare(ctx, req)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res, childCount+1)
|
|
|
|
for i := range res {
|
|
require.Equal(t, timeout, res[i].GetTimeout())
|
|
require.Equal(t, token, res[i].GetSessionToken())
|
|
require.Equal(t, uint32(service.NonForwardingTTL), res[i].GetTTL())
|
|
|
|
a := res[i].GetAddress()
|
|
require.Equal(t, addr.CID, a.CID)
|
|
if i > 0 {
|
|
require.Equal(t, children[i-1], a.ObjectID)
|
|
} else {
|
|
require.Equal(t, addr.ObjectID, a.ObjectID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_straightObjRemover_delete(t *testing.T) {
|
|
var (
|
|
ctx = context.TODO()
|
|
addr = testObjectAddress(t)
|
|
ttl = uint32(10)
|
|
timeout = 5 * time.Second
|
|
token = new(service.Token)
|
|
obj = &Object{SystemHeader: SystemHeader{ID: addr.ObjectID, CID: addr.CID}}
|
|
)
|
|
|
|
token.SetID(session.TokenID{1, 2, 3})
|
|
|
|
req := newRawDeleteInfo()
|
|
req.setTTL(ttl)
|
|
req.setTimeout(timeout)
|
|
req.setAddress(testObjectAddress(t))
|
|
req.setSessionToken(token)
|
|
|
|
t.Run("correct result", func(t *testing.T) {
|
|
osErr := internal.Error("test error for object storer")
|
|
|
|
s := &straightObjRemover{
|
|
tombCreator: &testDeleteEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct tombstone creator params", func(t *testing.T) {
|
|
require.Equal(t, req, items[0])
|
|
})
|
|
},
|
|
res: obj,
|
|
},
|
|
objStorer: &testDeleteEntity{
|
|
f: func(items ...interface{}) {
|
|
t.Run("correct object storer params", func(t *testing.T) {
|
|
p := items[0].(transport.PutInfo)
|
|
require.Equal(t, timeout, p.GetTimeout())
|
|
require.Equal(t, ttl, p.GetTTL())
|
|
require.Equal(t, obj, p.GetHead())
|
|
require.Equal(t, token, p.GetSessionToken())
|
|
})
|
|
},
|
|
err: osErr, // force objectStorer to return osErr
|
|
},
|
|
}
|
|
|
|
// ascertain that error returns as expected
|
|
require.EqualError(t, s.delete(ctx, req), osErr.Error())
|
|
})
|
|
}
|
|
|
|
func Test_coreTombCreator_createTombstone(t *testing.T) {
|
|
var (
|
|
ctx = context.TODO()
|
|
addr = testObjectAddress(t)
|
|
ownerID = OwnerID{1, 2, 3}
|
|
)
|
|
|
|
req := newRawDeleteInfo()
|
|
req.setAddress(addr)
|
|
req.setOwnerID(ownerID)
|
|
|
|
t.Run("correct result", func(t *testing.T) {
|
|
s := new(coreTombCreator)
|
|
|
|
res := s.createTombstone(ctx, req)
|
|
require.Equal(t, addr.CID, res.SystemHeader.CID)
|
|
require.Equal(t, addr.ObjectID, res.SystemHeader.ID)
|
|
require.Equal(t, ownerID, res.SystemHeader.OwnerID)
|
|
|
|
_, tsHdr := res.LastHeader(object.HeaderType(object.TombstoneHdr))
|
|
require.NotNil(t, tsHdr)
|
|
require.Equal(t, new(object.Tombstone), tsHdr.Value.(*object.Header_Tombstone).Tombstone)
|
|
})
|
|
}
|
|
|
|
func Test_deleteInfo(t *testing.T) {
|
|
t.Run("address", func(t *testing.T) {
|
|
addr := testObjectAddress(t)
|
|
|
|
req := newRawDeleteInfo()
|
|
req.setAddress(addr)
|
|
|
|
require.Equal(t, addr, req.GetAddress())
|
|
})
|
|
|
|
t.Run("owner ID", func(t *testing.T) {
|
|
ownerID := OwnerID{}
|
|
_, err := rand.Read(ownerID[:])
|
|
require.NoError(t, err)
|
|
|
|
req := newRawDeleteInfo()
|
|
req.setOwnerID(ownerID)
|
|
require.Equal(t, ownerID, req.GetOwnerID())
|
|
|
|
tReq := &transportRequest{serviceRequest: &object.DeleteRequest{OwnerID: ownerID}}
|
|
require.Equal(t, ownerID, tReq.GetOwnerID())
|
|
})
|
|
|
|
t.Run("token", func(t *testing.T) {
|
|
token := new(session.Token)
|
|
_, err := rand.Read(token.ID[:])
|
|
require.NoError(t, err)
|
|
|
|
req := newRawDeleteInfo()
|
|
req.setSessionToken(token)
|
|
require.Equal(t, token, req.GetSessionToken())
|
|
|
|
dReq := new(object.DeleteRequest)
|
|
dReq.SetToken(token)
|
|
tReq := &transportRequest{serviceRequest: dReq}
|
|
require.Equal(t, token, tReq.GetSessionToken())
|
|
})
|
|
}
|