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

450 lines
11 KiB
Go
Raw Normal View History

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