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

514 lines
12 KiB
Go
Raw Normal View History

package object
import (
"context"
"testing"
"time"
"github.com/nspcc-dev/neofs-api-go/query"
v1 "github.com/nspcc-dev/neofs-api-go/query"
"github.com/nspcc-dev/neofs-api-go/service"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/implementations"
"github.com/nspcc-dev/neofs-node/lib/test"
"github.com/nspcc-dev/neofs-node/lib/transport"
"github.com/stretchr/testify/require"
)
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.
testListingEntity struct {
// Set of interfaces which entity must implement, but some methods from those does not call.
implementations.SelectiveContainerExecutor
// 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 (
_ objectSearcher = (*testListingEntity)(nil)
_ selectiveRangeReceiver = (*testListingEntity)(nil)
_ implementations.SelectiveContainerExecutor = (*testListingEntity)(nil)
)
func (s *testListingEntity) rangeDescriptor(_ context.Context, a Address, f relationQueryFunc) (RangeDescriptor, error) {
if s.f != nil {
s.f(a, f)
}
if s.err != nil {
return RangeDescriptor{}, s.err
}
return s.res.(RangeDescriptor), nil
}
func (s *testListingEntity) Head(_ context.Context, p *implementations.HeadParams) error {
if s.f != nil {
s.f(p)
}
return s.err
}
func (s *testListingEntity) searchObjects(ctx context.Context, i transport.SearchInfo) ([]Address, error) {
if s.f != nil {
s.f(i)
}
if s.err != nil {
return nil, s.err
}
return s.res.([]Address), nil
}
func Test_rawSeachInfo(t *testing.T) {
t.Run("TTL", func(t *testing.T) {
ttl := uint32(3)
r := newRawSearchInfo()
r.setTTL(ttl)
require.Equal(t, ttl, r.GetTTL())
})
t.Run("timeout", func(t *testing.T) {
timeout := 3 * time.Second
r := newRawSearchInfo()
r.setTimeout(timeout)
require.Equal(t, timeout, r.GetTimeout())
})
t.Run("CID", func(t *testing.T) {
cid := testObjectAddress(t).CID
r := newRawSearchInfo()
r.setCID(cid)
require.Equal(t, cid, r.GetCID())
})
t.Run("query", func(t *testing.T) {
query := testData(t, 10)
r := newRawSearchInfo()
r.setQuery(query)
require.Equal(t, query, r.GetQuery())
})
}
func Test_coreChildrenQueryFunc(t *testing.T) {
t.Run("correct query composition", func(t *testing.T) {
// create custom address for test
addr := testObjectAddress(t)
res, err := coreChildrenQueryFunc(addr)
require.NoError(t, err)
// unmarshal query
q := v1.Query{}
require.NoError(t, q.Unmarshal(res))
// ascertain that filter list composed correctly
require.Len(t, q.Filters, 2)
require.Contains(t, q.Filters, QueryFilter{
Type: v1.Filter_Exact,
Name: transport.KeyHasParent,
})
require.Contains(t, q.Filters, QueryFilter{
Type: v1.Filter_Exact,
Name: transport.KeyParent,
Value: addr.ObjectID.String(),
})
})
}
func Test_coreChildrenLister_children(t *testing.T) {
ctx := context.TODO()
addr := testObjectAddress(t)
t.Run("query function failure", func(t *testing.T) {
s := &coreChildrenLister{
queryFn: func(v Address) ([]byte, error) {
t.Run("correct query function params", func(t *testing.T) {
require.Equal(t, addr, v)
})
return nil, internal.Error("") // force relationQueryFunc to return some non-nil error
},
log: test.NewTestLogger(false),
}
require.Empty(t, s.children(ctx, addr))
})
t.Run("object searcher failure", func(t *testing.T) {
// create custom timeout for test
sErr := internal.Error("test error for object searcher")
// create custom timeout for test
timeout := 3 * time.Second
// create custom query for test
query := testData(t, 10)
s := &coreChildrenLister{
queryFn: func(v Address) ([]byte, error) {
return query, nil // force relationQueryFunc to return created query
},
objSearcher: &testListingEntity{
f: func(items ...interface{}) {
t.Run("correct object searcher params", func(t *testing.T) {
p := items[0].(transport.SearchInfo)
require.Equal(t, timeout, p.GetTimeout())
require.Equal(t, query, p.GetQuery())
require.Equal(t, addr.CID, p.GetCID())
require.Equal(t, uint32(service.NonForwardingTTL), p.GetTTL())
})
},
err: sErr, // force objectSearcher to return sErr
},
log: test.NewTestLogger(false),
timeout: timeout,
}
require.Empty(t, s.children(ctx, addr))
})
t.Run("correct result", func(t *testing.T) {
// create custom child list
addrList := testAddrList(t, 5)
idList := make([]ID, 0, len(addrList))
for i := range addrList {
idList = append(idList, addrList[i].ObjectID)
}
s := &coreChildrenLister{
queryFn: func(address Address) ([]byte, error) {
return nil, nil // force relationQueryFunc to return nil error
},
objSearcher: &testListingEntity{
res: addrList,
},
}
require.Equal(t, idList, s.children(ctx, addr))
})
}
func Test_queryGenerators(t *testing.T) {
t.Run("object ID", func(t *testing.T) {
var (
q = new(query.Query)
key = "key for test"
id = testObjectAddress(t).ObjectID
)
res, err := idQueryFunc(key, id)
require.NoError(t, err)
require.NoError(t, q.Unmarshal(res))
require.Len(t, q.Filters, 1)
require.Equal(t, query.Filter{
Type: v1.Filter_Exact,
Name: key,
Value: id.String(),
}, q.Filters[0])
})
t.Run("left neighbor", func(t *testing.T) {
var (
q = new(query.Query)
addr = testObjectAddress(t)
)
res, err := leftNeighborQueryFunc(addr)
require.NoError(t, err)
require.NoError(t, q.Unmarshal(res))
require.Len(t, q.Filters, 1)
require.Equal(t, query.Filter{
Type: v1.Filter_Exact,
Name: KeyNext,
Value: addr.ObjectID.String(),
}, q.Filters[0])
})
t.Run("right neighbor", func(t *testing.T) {
var (
q = new(query.Query)
addr = testObjectAddress(t)
)
res, err := rightNeighborQueryFunc(addr)
require.NoError(t, err)
require.NoError(t, q.Unmarshal(res))
require.Len(t, q.Filters, 1)
require.Equal(t, query.Filter{
Type: v1.Filter_Exact,
Name: KeyPrev,
Value: addr.ObjectID.String(),
}, q.Filters[0])
})
t.Run("first child", func(t *testing.T) {
var (
q = new(query.Query)
addr = testObjectAddress(t)
)
res, err := firstChildQueryFunc(addr)
require.NoError(t, err)
require.NoError(t, q.Unmarshal(res))
require.Len(t, q.Filters, 3)
require.Contains(t, q.Filters, query.Filter{
Type: v1.Filter_Exact,
Name: transport.KeyHasParent,
})
require.Contains(t, q.Filters, query.Filter{
Type: v1.Filter_Exact,
Name: transport.KeyParent,
Value: addr.ObjectID.String(),
})
require.Contains(t, q.Filters, query.Filter{
Type: v1.Filter_Exact,
Name: KeyPrev,
Value: ID{}.String(),
})
})
}
func Test_selectiveRangeRecv(t *testing.T) {
ctx := context.TODO()
addr := testObjectAddress(t)
t.Run("query function failure", func(t *testing.T) {
qfErr := internal.Error("test error for query function")
_, err := new(selectiveRangeRecv).rangeDescriptor(ctx, testObjectAddress(t), func(Address) ([]byte, error) {
return nil, qfErr
})
require.EqualError(t, err, qfErr.Error())
})
t.Run("correct executor params", func(t *testing.T) {
t.Run("w/ query function", func(t *testing.T) {
qBytes := testData(t, 10)
s := &selectiveRangeRecv{
executor: &testListingEntity{
f: func(items ...interface{}) {
p := items[0].(*implementations.HeadParams)
require.Equal(t, addr.CID, p.CID)
require.True(t, p.ServeLocal)
require.Equal(t, uint32(service.SingleForwardingTTL), p.TTL)
require.True(t, p.FullHeaders)
require.Equal(t, qBytes, p.Query)
require.Empty(t, p.IDList)
},
},
}
_, _ = s.rangeDescriptor(ctx, addr, func(Address) ([]byte, error) { return qBytes, nil })
})
t.Run("w/o query function", func(t *testing.T) {
s := &selectiveRangeRecv{
executor: &testListingEntity{
f: func(items ...interface{}) {
p := items[0].(*implementations.HeadParams)
require.Equal(t, addr.CID, p.CID)
require.True(t, p.ServeLocal)
require.Equal(t, uint32(service.SingleForwardingTTL), p.TTL)
require.True(t, p.FullHeaders)
require.Empty(t, p.Query)
require.Equal(t, []ID{addr.ObjectID}, p.IDList)
},
},
}
_, _ = s.rangeDescriptor(ctx, addr, nil)
})
})
t.Run("correct result", func(t *testing.T) {
t.Run("failure", func(t *testing.T) {
t.Run("executor failure", func(t *testing.T) {
exErr := internal.Error("test error for executor")
s := &selectiveRangeRecv{
executor: &testListingEntity{
err: exErr,
},
}
_, err := s.rangeDescriptor(ctx, addr, nil)
require.EqualError(t, err, exErr.Error())
})
t.Run("not found", func(t *testing.T) {
s := &selectiveRangeRecv{
executor: new(testListingEntity),
}
_, err := s.rangeDescriptor(ctx, addr, nil)
require.EqualError(t, err, errRelationNotFound.Error())
})
})
t.Run("success", func(t *testing.T) {
foundAddr := testObjectAddress(t)
obj := &Object{
SystemHeader: SystemHeader{
PayloadLength: 100,
ID: foundAddr.ObjectID,
CID: foundAddr.CID,
},
}
s := &selectiveRangeRecv{
executor: &testListingEntity{
SelectiveContainerExecutor: nil,
f: func(items ...interface{}) {
p := items[0].(*implementations.HeadParams)
p.Handler(nil, obj)
},
},
}
res, err := s.rangeDescriptor(ctx, addr, nil)
require.NoError(t, err)
require.Equal(t, RangeDescriptor{
Size: int64(obj.SystemHeader.PayloadLength),
Offset: 0,
Addr: foundAddr,
LeftBound: true,
RightBound: true,
}, res)
})
})
}
func Test_neighborReceiver(t *testing.T) {
ctx := context.TODO()
addr := testObjectAddress(t)
t.Run("neighbor", func(t *testing.T) {
t.Run("correct internal logic", func(t *testing.T) {
rightCalled, leftCalled := false, false
s := &neighborReceiver{
leftNeighborQueryFn: func(a Address) ([]byte, error) {
require.Equal(t, addr, a)
leftCalled = true
return nil, nil
},
rightNeighborQueryFn: func(a Address) ([]byte, error) {
require.Equal(t, addr, a)
rightCalled = true
return nil, nil
},
rangeDescRecv: &testListingEntity{
f: func(items ...interface{}) {
require.Equal(t, addr, items[0])
_, _ = items[1].(relationQueryFunc)(addr)
},
err: internal.Error(""),
},
}
_, _ = s.Neighbor(ctx, addr, true)
require.False(t, rightCalled)
require.True(t, leftCalled)
leftCalled = false
_, _ = s.Neighbor(ctx, addr, false)
require.False(t, leftCalled)
require.True(t, rightCalled)
})
t.Run("correct result", func(t *testing.T) {
rErr := internal.Error("test error for range receiver")
rngRecv := &testListingEntity{err: rErr}
s := &neighborReceiver{rangeDescRecv: rngRecv}
_, err := s.Neighbor(ctx, addr, false)
require.EqualError(t, err, rErr.Error())
rngRecv.err = errRelationNotFound
_, err = s.Neighbor(ctx, addr, false)
require.EqualError(t, err, errRelationNotFound.Error())
rd := RangeDescriptor{Size: 1, Offset: 2, Addr: addr}
rngRecv.res, rngRecv.err = rd, nil
res, err := s.Neighbor(ctx, addr, false)
require.NoError(t, err)
require.Equal(t, rd, res)
})
})
t.Run("base", func(t *testing.T) {
rd := RangeDescriptor{Size: 1, Offset: 2, Addr: addr}
t.Run("first child exists", func(t *testing.T) {
called := false
s := &neighborReceiver{
firstChildQueryFn: func(a Address) ([]byte, error) {
require.Equal(t, addr, a)
called = true
return nil, nil
},
rangeDescRecv: &testListingEntity{
f: func(items ...interface{}) {
require.Equal(t, addr, items[0])
_, _ = items[1].(relationQueryFunc)(addr)
},
res: rd,
},
}
res, err := s.Base(ctx, addr)
require.NoError(t, err)
require.Equal(t, rd, res)
require.True(t, called)
})
t.Run("first child doesn't exist", func(t *testing.T) {
called := false
recv := &testListingEntity{err: internal.Error("")}
recv.f = func(...interface{}) {
if called {
recv.res, recv.err = rd, nil
}
called = true
}
s := &neighborReceiver{rangeDescRecv: recv}
res, err := s.Base(ctx, addr)
require.NoError(t, err)
require.Equal(t, rd, res)
})
})
}