package object import ( "bytes" "context" "io" "testing" "time" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/session" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" "github.com/nspcc-dev/neofs-node/pkg/util/test" "github.com/pkg/errors" "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. testPutEntity struct { // Set of interfaces which entity must implement, but some methods from those does not call. object.Service_PutServer transport.PutInfo Filter session.PrivateTokenStore transport.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 ( _ object.Service_PutServer = (*testPutEntity)(nil) _ requestHandler = (*testPutEntity)(nil) _ objectStorer = (*testPutEntity)(nil) _ transport.PutInfo = (*testPutEntity)(nil) _ Filter = (*testPutEntity)(nil) _ operationExecutor = (*testPutEntity)(nil) _ session.PrivateTokenStore = (*testPutEntity)(nil) _ EpochReceiver = (*testPutEntity)(nil) _ transformer.Transformer = (*testPutEntity)(nil) ) func (s *testPutEntity) Verify(_ context.Context, obj *Object) error { if s.f != nil { s.f(obj) } return s.err } func (s *testPutEntity) Transform(_ context.Context, u transformer.ProcUnit, h ...transformer.ProcUnitHandler) error { if s.f != nil { s.f(u, h) } return s.err } func (s *testPutEntity) verify(_ context.Context, token *session.Token, obj *Object) error { if s.f != nil { s.f(token, obj) } return s.err } func (s *testPutEntity) Epoch() uint64 { return s.res.(uint64) } func (s *testPutEntity) Direct(ctx context.Context, objs ...Object) ([]Object, error) { if s.f != nil { s.f(ctx, objs) } return s.res.([]Object), s.err } func (s *testPutEntity) 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 *testPutEntity) executeOperation(_ context.Context, m transport.MetaInfo, h responseItemHandler) error { if s.f != nil { s.f(m, h) } return s.err } func (s *testPutEntity) Pass(ctx context.Context, m *Meta) *localstore.FilterResult { if s.f != nil { s.f(ctx, m) } items := s.res.([]interface{}) return items[0].(*localstore.FilterResult) } func (s *testPutEntity) GetTTL() uint32 { return s.res.(uint32) } func (s *testPutEntity) GetToken() *session.Token { return s.res.(*session.Token) } func (s *testPutEntity) GetHead() *Object { return s.res.(*Object) } func (s *testPutEntity) putObject(ctx context.Context, p transport.PutInfo) (*Address, error) { if s.f != nil { s.f(p, ctx) } if s.err != nil { return nil, s.err } return s.res.(*Address), nil } func (s *testPutEntity) handleRequest(_ context.Context, p handleRequestParams) (interface{}, error) { if s.f != nil { s.f(p) } return s.res, s.err } func (s *testPutEntity) Recv() (*object.PutRequest, error) { if s.f != nil { s.f() } if s.err != nil { return nil, s.err } else if s.res == nil { return nil, nil } return s.res.(*object.PutRequest), nil } func (s *testPutEntity) Context() context.Context { return context.TODO() } func Test_objectService_Put(t *testing.T) { t.Run("stream error", func(t *testing.T) { // create custom error for test psErr := errors.New("test error for put stream server") s := &testPutEntity{ err: psErr, // force server to return psErr } srv := &objectService{ statusCalculator: newStatusCalculator(), } // ascertain that error returns as expected require.EqualError(t, srv.Put(s), psErr.Error(), ) }) t.Run("request handling", func(t *testing.T) { // create custom request for test req := &object.PutRequest{R: &object.PutRequest_Header{ Header: &object.PutRequest_PutHeader{ Object: new(Object), }, }} // create custom error for test hErr := errors.New("test error for request handler") srv := &testPutEntity{ res: req, // force server to return req } s := &objectService{ statusCalculator: newStatusCalculator(), } s.requestHandler = &testPutEntity{ f: func(items ...interface{}) { t.Run("correct request handler params", func(t *testing.T) { p := items[0].(handleRequestParams) require.Equal(t, s, p.executor) require.Equal(t, &putRequest{ PutRequest: req, srv: srv, }, p.request) }) }, err: hErr, // force requestHandler to return hErr } // ascertain that error returns as expected require.EqualError(t, s.Put(srv), hErr.Error(), ) }) } func Test_straightObjectStorer_putObject(t *testing.T) { ctx := context.TODO() t.Run("executor error", func(t *testing.T) { // create custom error for test exErr := errors.New("test error for operation executor") // create custom meta info for test info := new(testPutEntity) s := &straightObjectStorer{ executor: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct operation executor params", func(t *testing.T) { require.Equal(t, info, items[0]) acc := items[1].(*coreAddrAccum) require.NotNil(t, acc.Once) }) }, err: exErr, }, } _, err := s.putObject(ctx, info) // ascertain that error returns as expected require.EqualError(t, err, exErr.Error()) }) t.Run("correct result", func(t *testing.T) { addr := testObjectAddress(t) s := &straightObjectStorer{ executor: &testPutEntity{ f: func(items ...interface{}) { // add address to accumulator items[1].(addressAccumulator).handleItem(&addr) }, }, } res, err := s.putObject(ctx, new(testPutEntity)) require.NoError(t, err) // ascertain that result returns as expected require.Equal(t, &addr, res) }) } func Test_recvPutHeaderMsg(t *testing.T) { t.Run("server error", func(t *testing.T) { // create custom error for test srvErr := errors.New("test error for put server") srv := &testPutEntity{ err: srvErr, // force put server to return srvErr } res, err := recvPutHeaderMsg(srv) // ascertain that error returns as expected require.EqualError(t, err, srvErr.Error()) require.Nil(t, res) }) t.Run("empty message", func(t *testing.T) { srv := &testPutEntity{ res: nil, // force put server to return nil, nil } res, err := recvPutHeaderMsg(srv) // ascertain that error returns as expected require.EqualError(t, err, errHeaderExpected.Error()) require.Nil(t, res) }) t.Run("empty put header in message", func(t *testing.T) { srv := &testPutEntity{ res: new(object.PutRequest), // force put server to return message w/o put header } res, err := recvPutHeaderMsg(srv) // ascertain that error returns as expected require.EqualError(t, err, object.ErrHeaderExpected.Error()) require.Nil(t, res) }) t.Run("empty object in put header", func(t *testing.T) { srv := &testPutEntity{ res: object.MakePutRequestHeader(nil), // force put server to return message w/ nil object } res, err := recvPutHeaderMsg(srv) // ascertain that error returns as expected require.EqualError(t, err, errObjectExpected.Error()) require.Nil(t, res) }) } func Test_putRequest(t *testing.T) { t.Run("timeout", func(t *testing.T) { timeout := 3 * time.Second req := &putRequest{ timeout: timeout, } // ascertain that timeout returns as expected require.Equal(t, timeout, req.GetTimeout()) }) t.Run("head", func(t *testing.T) { // create custom object for test obj := new(Object) req := &putRequest{ PutRequest: object.MakePutRequestHeader(obj), // wrap object to test message } // ascertain that head returns as expected require.Equal(t, obj, req.GetHead()) }) t.Run("payload", func(t *testing.T) { req := &putRequest{ srv: new(testPutEntity), } require.Equal(t, &putStreamReader{srv: req.srv}, req.Payload()) }) t.Run("copies number", func(t *testing.T) { cn := uint32(5) req := &putRequest{ PutRequest: &object.PutRequest{ R: &object.PutRequest_Header{ Header: &object.PutRequest_PutHeader{ CopiesNumber: cn, }, }, }, } require.Equal(t, cn, req.CopiesNumber()) }) } func Test_coreAddrAccum(t *testing.T) { t.Run("new", func(t *testing.T) { s := newAddressAccumulator() // ascertain that type is correct and Once entity initialize require.NotNil(t, s.(*coreAddrAccum).Once) }) t.Run("address", func(t *testing.T) { addr := testObjectAddress(t) s := &coreAddrAccum{addr: &addr} // ascertain that address returns as expected require.Equal(t, &addr, s.address()) }) t.Run("handle", func(t *testing.T) { addr := testObjectAddress(t) s := newAddressAccumulator() s.handleItem(&addr) // ascertain that address saved require.Equal(t, &addr, s.address()) // create another address for test addr2 := testObjectAddress(t) s.handleItem(&addr2) // ascertain that second address is ignored require.Equal(t, &addr, s.address()) }) } func Test_rawPutInfo(t *testing.T) { t.Run("TTL", func(t *testing.T) { ttl := uint32(3) s := newRawPutInfo() s.setTTL(ttl) require.Equal(t, ttl, s.GetTTL()) }) t.Run("head", func(t *testing.T) { obj := new(Object) s := newRawPutInfo() s.setHead(obj) require.Equal(t, obj, s.GetHead()) }) t.Run("payload", func(t *testing.T) { // ascertain that nil chunk returns as expected r := bytes.NewBuffer(nil) req := newRawPutInfo() req.setPayload(r) require.Equal(t, r, req.Payload()) }) t.Run("token", func(t *testing.T) { // ascertain that nil token returns as expected require.Nil(t, newRawPutInfo().GetSessionToken()) }) t.Run("copies number", func(t *testing.T) { cn := uint32(100) s := newRawPutInfo() s.setCopiesNumber(cn) require.Equal(t, cn, s.CopiesNumber()) }) } func Test_contextWithValues(t *testing.T) { k1, k2 := "key 1", "key2" v1, v2 := "value 1", "value 2" ctx := contextWithValues(context.TODO(), k1, v1, k2, v2) // ascertain that all values added require.Equal(t, v1, ctx.Value(k1)) require.Equal(t, v2, ctx.Value(k2)) } func Test_bifurcatingObjectStorer(t *testing.T) { ctx := context.TODO() // create custom error for test sErr := errors.New("test error for object storer") t.Run("w/ token", func(t *testing.T) { // create custom request w/ token sk := test.DecodeKey(0) owner, err := refs.NewOwnerID(&sk.PublicKey) require.NoError(t, err) token := new(service.Token) token.SetOwnerID(owner) req := &putRequest{ PutRequest: object.MakePutRequestHeader(new(Object)), } req.SetToken(token) require.NoError(t, requestSignFunc(sk, req)) s := &bifurcatingObjectStorer{ tokenStorer: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct token storer params", func(t *testing.T) { require.Equal(t, req, items[0]) }) }, err: sErr, // force token storer to return sErr }, } _, err = s.putObject(ctx, req) require.EqualError(t, err, sErr.Error()) }) t.Run("w/o token", func(t *testing.T) { // create custom request w/o token req := newRawPutInfo() require.Nil(t, req.GetSessionToken()) s := &bifurcatingObjectStorer{ straightStorer: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct token storer params", func(t *testing.T) { require.Equal(t, req, items[0]) }) }, err: sErr, // force token storer to return sErr }, } _, err := s.putObject(ctx, req) require.EqualError(t, err, sErr.Error()) }) } func TestWithTokenFromOwner(t *testing.T) { // nil request require.False(t, withTokenFromOwner(nil)) // create test request req := &putRequest{ PutRequest: new(object.PutRequest), } // w/o session token require.Nil(t, req.GetSessionToken()) require.False(t, withTokenFromOwner(req)) // create test session token and add it to request token := new(service.Token) req.SetToken(token) // w/o signatures require.False(t, withTokenFromOwner(req)) // create test public key pk := &test.DecodeKey(0).PublicKey // add key-signature pair req.AddSignKey(nil, pk) // wrong token owner require.False(t, withTokenFromOwner(req)) // set correct token owner owner, err := refs.NewOwnerID(pk) require.NoError(t, err) token.SetOwnerID(owner) require.True(t, withTokenFromOwner(req)) } func Test_tokenObjectStorer(t *testing.T) { ctx := context.TODO() token := new(service.Token) token.SetID(session.TokenID{1, 2, 3}) token.SetSignature(testData(t, 10)) // create custom request w/ token and object for test req := newRawPutInfo() req.setSessionToken(token) req.setHead(&Object{ Payload: testData(t, 10), }) t.Run("token store failure", func(t *testing.T) { s := &tokenObjectStorer{ tokenStore: &testPutEntity{ err: errors.New(""), // force token store to return a non-nil error }, } _, err := s.putObject(ctx, req) require.EqualError(t, err, errTokenRetrieval.Error()) }) t.Run("correct result", func(t *testing.T) { addr := testObjectAddress(t) pToken, err := session.NewPrivateToken(0) require.NoError(t, err) s := &tokenObjectStorer{ tokenStore: &testPutEntity{ res: pToken, }, objStorer: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct object storer params", func(t *testing.T) { require.Equal(t, req, items[0]) ctx := items[1].(context.Context) require.Equal(t, pToken, ctx.Value(transformer.PrivateSessionToken)) require.Equal(t, token, ctx.Value(transformer.PublicSessionToken)) }) }, res: &addr, }, } res, err := s.putObject(ctx, req) require.NoError(t, err) require.Equal(t, addr, *res) }) } func Test_filteringObjectStorer(t *testing.T) { ctx := context.TODO() t.Run("filter failure", func(t *testing.T) { var ( ttl = uint32(5) obj = &Object{Payload: testData(t, 10)} ) req := newRawPutInfo() req.setHead(obj) req.setTTL(ttl) s := &filteringObjectStorer{ filter: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct filter params", func(t *testing.T) { require.Equal(t, &Meta{Object: obj}, items[1]) ctx := items[0].(context.Context) require.Equal(t, ttl, ctx.Value(ttlValue)) }) }, res: []interface{}{localstore.ResultFail()}, }, } _, err := s.putObject(ctx, req) require.EqualError(t, err, errObjectFilter.Error()) }) t.Run("correct result", func(t *testing.T) { req := newRawPutInfo() req.setHead(&Object{ Payload: testData(t, 10), }) addr := testObjectAddress(t) s := &filteringObjectStorer{ filter: &testPutEntity{ res: []interface{}{localstore.ResultPass()}, }, objStorer: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct object storer params", func(t *testing.T) { require.Equal(t, req, items[0]) }) }, res: &addr, }, } res, err := s.putObject(ctx, req) require.NoError(t, err) require.Equal(t, &addr, res) }) } func Test_receivingObjectStorer(t *testing.T) { ctx := context.TODO() t.Run("cut payload", func(t *testing.T) { payload := testData(t, 10) req := newRawPutInfo() req.setHead(&Object{ SystemHeader: SystemHeader{ PayloadLength: uint64(len(payload)) + 1, }, }) req.setPayload(bytes.NewBuffer(payload)) _, err := new(receivingObjectStorer).putObject(ctx, req) require.EqualError(t, err, transformer.ErrPayloadEOF.Error()) }) t.Run("payload verification failure", func(t *testing.T) { vErr := errors.New("payload verification error for test") req := newRawPutInfo() req.setHead(&Object{ Payload: testData(t, 10), }) s := &receivingObjectStorer{ vPayload: &testPutEntity{ f: func(items ...interface{}) { require.Equal(t, req.obj, items[0]) }, err: vErr, }, } _, err := s.putObject(ctx, req) require.EqualError(t, err, errPayloadChecksum.Error()) }) t.Run("correct result", func(t *testing.T) { var ( cn = uint32(10) ttl = uint32(5) timeout = 3 * time.Second payload = testData(t, 10) addr = testObjectAddress(t) ) obj := &Object{ SystemHeader: SystemHeader{ PayloadLength: uint64(len(payload)), ID: addr.ObjectID, CID: addr.CID, }, } req := newRawPutInfo() req.setHead(obj) req.setPayload(bytes.NewBuffer(payload)) req.setTimeout(timeout) req.setTTL(ttl) req.setCopiesNumber(cn) req.setSessionToken(new(service.Token)) s := &receivingObjectStorer{ straightStorer: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct straight storer params", func(t *testing.T) { exp := newRawPutInfo() exp.setHead(obj) exp.setTimeout(timeout) exp.setTTL(ttl) exp.setCopiesNumber(cn) exp.setSessionToken(req.GetSessionToken()) require.Equal(t, exp, items[0]) }) }, res: &addr, }, vPayload: new(testPutEntity), } res, err := s.putObject(ctx, req) require.NoError(t, err) require.Equal(t, &addr, res) }) } func Test_transformingObjectStorer(t *testing.T) { ctx := context.TODO() t.Run("correct behavior", func(t *testing.T) { var ( tErr = errors.New("test error for transformer") addr = testObjectAddress(t) obj = &Object{ SystemHeader: SystemHeader{ ID: addr.ObjectID, CID: addr.CID, }, Payload: testData(t, 10), } ) req := newRawPutInfo() req.setHead(obj) req.setPayload(bytes.NewBuffer(obj.Payload)) req.setTimeout(3 * time.Second) req.setTTL(5) req.setCopiesNumber(100) req.setSessionToken(new(service.Token)) tr := &testPutEntity{ f: func(items ...interface{}) { t.Run("correct transformer params", func(t *testing.T) { require.Equal(t, transformer.ProcUnit{ Head: req.obj, Payload: req.r, }, items[0]) fns := items[1].([]transformer.ProcUnitHandler) require.Len(t, fns, 1) _ = fns[0](ctx, transformer.ProcUnit{ Head: req.obj, Payload: req.r, }) }) }, } s := &transformingObjectStorer{ transformer: tr, objStorer: &testPutEntity{ f: func(items ...interface{}) { t.Run("correct object storer params", func(t *testing.T) { exp := newRawPutInfo() exp.setHead(req.GetHead()) exp.setPayload(req.Payload()) exp.setTimeout(req.GetTimeout()) exp.setTTL(req.GetTTL()) exp.setCopiesNumber(req.CopiesNumber()) exp.setSessionToken(req.GetSessionToken()) require.Equal(t, exp, items[0]) }) }, err: errors.New(""), }, mErr: map[error]struct{}{ tErr: {}, }, } res, err := s.putObject(ctx, req) require.NoError(t, err) require.Equal(t, &addr, res) tr.err = tErr _, err = s.putObject(ctx, req) require.EqualError(t, err, tErr.Error()) tr.err = errors.New("some other error") _, err = s.putObject(ctx, req) require.EqualError(t, err, errTransformer.Error()) e := &transformerHandlerErr{ error: errors.New("transformer handler error"), } tr.err = e _, err = s.putObject(ctx, req) require.EqualError(t, err, e.error.Error()) }) } func Test_putStreamReader(t *testing.T) { t.Run("empty server", func(t *testing.T) { s := new(putStreamReader) n, err := s.Read(make([]byte, 1)) require.EqualError(t, err, io.EOF.Error()) require.Zero(t, n) }) t.Run("fail presence", func(t *testing.T) { initTail := testData(t, 10) s := putStreamReader{ tail: initTail, srv: new(testPutEntity), } buf := make([]byte, len(s.tail)/2) n, err := s.Read(buf) require.NoError(t, err) require.Equal(t, len(buf), n) require.Equal(t, buf, initTail[:n]) require.Equal(t, initTail[n:], s.tail) }) t.Run("receive message failure", func(t *testing.T) { t.Run("stream problem", func(t *testing.T) { srvErr := errors.New("test error for stream server") s := &putStreamReader{ srv: &testPutEntity{ err: srvErr, }, } n, err := s.Read(make([]byte, 1)) require.EqualError(t, err, srvErr.Error()) require.Zero(t, n) }) t.Run("incorrect chunk", func(t *testing.T) { t.Run("empty data", func(t *testing.T) { s := &putStreamReader{ srv: &testPutEntity{ res: object.MakePutRequestChunk(make([]byte, 0)), }, } n, err := s.Read(make([]byte, 1)) require.EqualError(t, err, errChunkExpected.Error()) require.Zero(t, n) }) t.Run("wrong message type", func(t *testing.T) { s := &putStreamReader{ srv: &testPutEntity{ res: object.MakePutRequestHeader(new(Object)), }, } n, err := s.Read(make([]byte, 1)) require.EqualError(t, err, errChunkExpected.Error()) require.Zero(t, n) }) }) }) t.Run("correct read", func(t *testing.T) { chunk := testData(t, 10) buf := make([]byte, len(chunk)/2) s := &putStreamReader{ srv: &testPutEntity{ res: object.MakePutRequestChunk(chunk), }, } n, err := s.Read(buf) require.NoError(t, err) require.Equal(t, chunk[:n], buf) require.Equal(t, chunk[n:], s.tail) }) t.Run("ful read", func(t *testing.T) { var ( callNum = 0 chunk1, chunk2 = testData(t, 100), testData(t, 88) ) srv := new(testPutEntity) srv.f = func(items ...interface{}) { if callNum == 0 { srv.res = object.MakePutRequestChunk(chunk1) } else if callNum == 1 { srv.res = object.MakePutRequestChunk(chunk2) } else { srv.res, srv.err = 0, io.EOF } callNum++ } s := &putStreamReader{ srv: srv, } var ( n int err error res = make([]byte, 0) buf = make([]byte, 10) ) for err != io.EOF { n, err = s.Read(buf) res = append(res, buf[:n]...) } require.Equal(t, append(chunk1, chunk2...), res) }) }