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

443 lines
11 KiB
Go
Raw Normal View History

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