forked from TrueCloudLab/frostfs-node
443 lines
11 KiB
Go
443 lines
11 KiB
Go
|
package object
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/nspcc-dev/neofs-api-go/object"
|
||
|
"github.com/nspcc-dev/neofs-node/internal"
|
||
|
"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.
|
||
|
testHandlerEntity struct {
|
||
|
// Set of interfaces which testCommonEntity must implement, but some methods from those does not call.
|
||
|
serviceRequest
|
||
|
object.Service_PutServer
|
||
|
|
||
|
// 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 (
|
||
|
_ requestPreProcessor = (*testHandlerEntity)(nil)
|
||
|
_ requestPostProcessor = (*testHandlerEntity)(nil)
|
||
|
_ requestHandleExecutor = (*testHandlerEntity)(nil)
|
||
|
_ objectSearcher = (*testHandlerEntity)(nil)
|
||
|
_ objectStorer = (*testHandlerEntity)(nil)
|
||
|
_ object.Service_PutServer = (*testHandlerEntity)(nil)
|
||
|
_ objectRemover = (*testHandlerEntity)(nil)
|
||
|
_ objectReceiver = (*testHandlerEntity)(nil)
|
||
|
_ objectRangeReceiver = (*testHandlerEntity)(nil)
|
||
|
_ payloadRangeReceiver = (*testHandlerEntity)(nil)
|
||
|
_ responsePreparer = (*testHandlerEntity)(nil)
|
||
|
)
|
||
|
|
||
|
func (s *testHandlerEntity) prepareResponse(_ context.Context, req serviceRequest, resp serviceResponse) error {
|
||
|
if s.f != nil {
|
||
|
s.f(req, resp)
|
||
|
}
|
||
|
return s.err
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) getRangeData(_ context.Context, info transport.RangeInfo, l ...Object) (io.Reader, error) {
|
||
|
if s.f != nil {
|
||
|
s.f(info, l)
|
||
|
}
|
||
|
if s.err != nil {
|
||
|
return nil, s.err
|
||
|
}
|
||
|
return s.res.(io.Reader), nil
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) getRange(_ context.Context, r rangeTool) (interface{}, error) {
|
||
|
if s.f != nil {
|
||
|
s.f(r)
|
||
|
}
|
||
|
return s.res, s.err
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) getObject(_ context.Context, r ...transport.GetInfo) (*objectData, error) {
|
||
|
if s.f != nil {
|
||
|
s.f(r)
|
||
|
}
|
||
|
if s.err != nil {
|
||
|
return nil, s.err
|
||
|
}
|
||
|
return s.res.(*objectData), nil
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) delete(_ context.Context, r deleteInfo) error {
|
||
|
if s.f != nil {
|
||
|
s.f(r)
|
||
|
}
|
||
|
return s.err
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) SendAndClose(r *object.PutResponse) error {
|
||
|
if s.f != nil {
|
||
|
s.f(r)
|
||
|
}
|
||
|
return s.err
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) putObject(_ context.Context, r transport.PutInfo) (*Address, error) {
|
||
|
if s.f != nil {
|
||
|
s.f(r)
|
||
|
}
|
||
|
if s.err != nil {
|
||
|
return nil, s.err
|
||
|
}
|
||
|
return s.res.(*Address), nil
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) searchObjects(_ context.Context, r transport.SearchInfo) ([]Address, error) {
|
||
|
if s.f != nil {
|
||
|
s.f(r)
|
||
|
}
|
||
|
if s.err != nil {
|
||
|
return nil, s.err
|
||
|
}
|
||
|
return s.res.([]Address), nil
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) preProcess(_ context.Context, req serviceRequest) error {
|
||
|
if s.f != nil {
|
||
|
s.f(req)
|
||
|
}
|
||
|
return s.err
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) postProcess(_ context.Context, req serviceRequest, e error) {
|
||
|
if s.f != nil {
|
||
|
s.f(req, e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *testHandlerEntity) executeRequest(_ context.Context, req serviceRequest) (interface{}, error) {
|
||
|
if s.f != nil {
|
||
|
s.f(req)
|
||
|
}
|
||
|
return s.res, s.err
|
||
|
}
|
||
|
|
||
|
func TestCoreRequestHandler_HandleRequest(t *testing.T) {
|
||
|
ctx := context.TODO()
|
||
|
|
||
|
// create custom serviceRequest
|
||
|
req := new(testHandlerEntity)
|
||
|
|
||
|
t.Run("pre processor error", func(t *testing.T) {
|
||
|
// create custom error
|
||
|
pErr := internal.Error("test error for pre-processor")
|
||
|
|
||
|
s := &coreRequestHandler{
|
||
|
preProc: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
t.Run("correct pre processor params", func(t *testing.T) {
|
||
|
require.Equal(t, req, items[0].(serviceRequest))
|
||
|
})
|
||
|
},
|
||
|
err: pErr, // force requestPreProcessor to return pErr
|
||
|
},
|
||
|
}
|
||
|
|
||
|
res, err := s.handleRequest(ctx, handleRequestParams{request: req})
|
||
|
|
||
|
// ascertain that error returns as expected
|
||
|
require.EqualError(t, err, pErr.Error())
|
||
|
|
||
|
// ascertain that nil result returns as expected
|
||
|
require.Nil(t, res)
|
||
|
})
|
||
|
|
||
|
t.Run("correct behavior", func(t *testing.T) {
|
||
|
// create custom error
|
||
|
eErr := internal.Error("test error for request executor")
|
||
|
|
||
|
// create custom result
|
||
|
eRes := testData(t, 10)
|
||
|
|
||
|
// create channel for requestPostProcessor
|
||
|
ch := make(chan struct{})
|
||
|
|
||
|
executor := &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
t.Run("correct executor params", func(t *testing.T) {
|
||
|
require.Equal(t, req, items[0].(serviceRequest))
|
||
|
})
|
||
|
},
|
||
|
res: eRes, // force requestHandleExecutor to return created result
|
||
|
err: eErr, // force requestHandleExecutor to return created error
|
||
|
}
|
||
|
|
||
|
s := &coreRequestHandler{
|
||
|
preProc: &testHandlerEntity{
|
||
|
err: nil, // force requestPreProcessor to return nil error
|
||
|
},
|
||
|
postProc: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
t.Run("correct pre processor params", func(t *testing.T) {
|
||
|
require.Equal(t, req, items[0].(serviceRequest))
|
||
|
require.Equal(t, eErr, items[1].(error))
|
||
|
})
|
||
|
ch <- struct{}{} // write to channel
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
res, err := s.handleRequest(ctx, handleRequestParams{
|
||
|
request: req,
|
||
|
executor: executor,
|
||
|
})
|
||
|
|
||
|
// ascertain that results return as expected
|
||
|
require.EqualError(t, err, eErr.Error())
|
||
|
require.Equal(t, eRes, res)
|
||
|
|
||
|
<-ch // read from channel
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func Test_objectService_executeRequest(t *testing.T) {
|
||
|
ctx := context.TODO()
|
||
|
|
||
|
t.Run("invalid request", func(t *testing.T) {
|
||
|
req := new(testHandlerEntity)
|
||
|
require.PanicsWithValue(t, fmt.Sprintf(pmWrongRequestType, req), func() {
|
||
|
_, _ = new(objectService).executeRequest(ctx, req)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.Run("search request", func(t *testing.T) {
|
||
|
var (
|
||
|
timeout = 3 * time.Second
|
||
|
req = &object.SearchRequest{ContainerID: testObjectAddress(t).CID}
|
||
|
addrList = testAddrList(t, 3)
|
||
|
)
|
||
|
|
||
|
s := &objectService{
|
||
|
pSrch: OperationParams{Timeout: timeout},
|
||
|
objSearcher: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, &transportRequest{
|
||
|
serviceRequest: req,
|
||
|
timeout: timeout,
|
||
|
}, items[0])
|
||
|
},
|
||
|
res: addrList,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
res, err := s.executeRequest(ctx, req)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, addrList, res)
|
||
|
})
|
||
|
|
||
|
t.Run("put request", func(t *testing.T) {
|
||
|
t.Run("storer error", func(t *testing.T) {
|
||
|
sErr := internal.Error("test error for object storer")
|
||
|
|
||
|
req := &putRequest{
|
||
|
PutRequest: new(object.PutRequest),
|
||
|
srv: new(testHandlerEntity),
|
||
|
timeout: 3 * time.Second,
|
||
|
}
|
||
|
|
||
|
s := &objectService{
|
||
|
objStorer: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, req, items[0])
|
||
|
},
|
||
|
err: sErr,
|
||
|
},
|
||
|
respPreparer: &testHandlerEntity{
|
||
|
res: serviceResponse(nil),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
_, err := s.executeRequest(ctx, req)
|
||
|
require.EqualError(t, err, sErr.Error())
|
||
|
})
|
||
|
|
||
|
t.Run("correct result", func(t *testing.T) {
|
||
|
addr := testObjectAddress(t)
|
||
|
|
||
|
srvErr := internal.Error("test error for stream server")
|
||
|
|
||
|
resp := &object.PutResponse{Address: addr}
|
||
|
|
||
|
pReq := new(object.PutRequest)
|
||
|
|
||
|
s := &objectService{
|
||
|
objStorer: &testHandlerEntity{
|
||
|
res: &addr,
|
||
|
},
|
||
|
respPreparer: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, pReq, items[0])
|
||
|
require.Equal(t, makePutResponse(addr), items[1])
|
||
|
},
|
||
|
res: resp,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
req := &putRequest{
|
||
|
PutRequest: pReq,
|
||
|
srv: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, resp, items[0])
|
||
|
},
|
||
|
err: srvErr,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
res, err := s.executeRequest(ctx, req)
|
||
|
require.EqualError(t, err, srvErr.Error())
|
||
|
require.Nil(t, res)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.Run("delete request", func(t *testing.T) {
|
||
|
var (
|
||
|
timeout = 3 * time.Second
|
||
|
dErr = internal.Error("test error for object remover")
|
||
|
req = &object.DeleteRequest{Address: testObjectAddress(t)}
|
||
|
)
|
||
|
|
||
|
s := &objectService{
|
||
|
objRemover: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, &transportRequest{
|
||
|
serviceRequest: req,
|
||
|
timeout: timeout,
|
||
|
}, items[0])
|
||
|
},
|
||
|
err: dErr,
|
||
|
},
|
||
|
pDel: OperationParams{Timeout: timeout},
|
||
|
}
|
||
|
|
||
|
res, err := s.executeRequest(ctx, req)
|
||
|
require.EqualError(t, err, dErr.Error())
|
||
|
require.Nil(t, res)
|
||
|
})
|
||
|
|
||
|
t.Run("get request", func(t *testing.T) {
|
||
|
var (
|
||
|
timeout = 3 * time.Second
|
||
|
obj = &objectData{Object: &Object{Payload: testData(t, 10)}}
|
||
|
req = &object.GetRequest{Address: testObjectAddress(t)}
|
||
|
)
|
||
|
|
||
|
s := &objectService{
|
||
|
objRecv: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, []transport.GetInfo{&transportRequest{
|
||
|
serviceRequest: req,
|
||
|
timeout: timeout,
|
||
|
}}, items[0])
|
||
|
},
|
||
|
res: obj,
|
||
|
},
|
||
|
pGet: OperationParams{Timeout: timeout},
|
||
|
}
|
||
|
|
||
|
res, err := s.executeRequest(ctx, req)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, obj, res)
|
||
|
})
|
||
|
|
||
|
t.Run("head request", func(t *testing.T) {
|
||
|
var (
|
||
|
timeout = 3 * time.Second
|
||
|
hErr = internal.Error("test error for head receiver")
|
||
|
req = &object.HeadRequest{Address: testObjectAddress(t)}
|
||
|
)
|
||
|
|
||
|
s := &objectService{
|
||
|
objRecv: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, []transport.GetInfo{&transportRequest{
|
||
|
serviceRequest: req,
|
||
|
timeout: timeout,
|
||
|
}}, items[0])
|
||
|
},
|
||
|
err: hErr,
|
||
|
},
|
||
|
pHead: OperationParams{Timeout: timeout},
|
||
|
}
|
||
|
|
||
|
_, err := s.executeRequest(ctx, req)
|
||
|
require.EqualError(t, err, hErr.Error())
|
||
|
})
|
||
|
|
||
|
t.Run("range requests", func(t *testing.T) {
|
||
|
t.Run("data", func(t *testing.T) {
|
||
|
var (
|
||
|
timeout = 3 * time.Second
|
||
|
rData = testData(t, 10)
|
||
|
req = &GetRangeRequest{Address: testObjectAddress(t)}
|
||
|
)
|
||
|
|
||
|
s := &objectService{
|
||
|
payloadRngRecv: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, &transportRequest{
|
||
|
serviceRequest: req,
|
||
|
timeout: timeout,
|
||
|
}, items[0])
|
||
|
require.Empty(t, items[1])
|
||
|
},
|
||
|
res: bytes.NewReader(rData),
|
||
|
},
|
||
|
pRng: OperationParams{Timeout: timeout},
|
||
|
}
|
||
|
|
||
|
res, err := s.executeRequest(ctx, req)
|
||
|
require.NoError(t, err)
|
||
|
d, err := ioutil.ReadAll(res.(io.Reader))
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, rData, d)
|
||
|
})
|
||
|
|
||
|
t.Run("hashes", func(t *testing.T) {
|
||
|
var (
|
||
|
timeout = 3 * time.Second
|
||
|
rErr = internal.Error("test error for range receiver")
|
||
|
req = &object.GetRangeHashRequest{Address: testObjectAddress(t)}
|
||
|
)
|
||
|
|
||
|
s := &objectService{
|
||
|
rngRecv: &testHandlerEntity{
|
||
|
f: func(items ...interface{}) {
|
||
|
require.Equal(t, &transportRequest{
|
||
|
serviceRequest: req,
|
||
|
timeout: timeout,
|
||
|
}, items[0])
|
||
|
},
|
||
|
err: rErr,
|
||
|
},
|
||
|
pRng: OperationParams{Timeout: timeout},
|
||
|
}
|
||
|
|
||
|
_, err := s.executeRequest(ctx, req)
|
||
|
require.EqualError(t, err, rErr.Error())
|
||
|
})
|
||
|
})
|
||
|
}
|