forked from TrueCloudLab/frostfs-sdk-go
[#270] client/netmap: Cover NetMapSnapshot
with unit tests
There is a need to test each `Client` operation. In previous implementation `Client` was based on real socket connection. This didn't allow to test the `Client` without OS resources. In order to write convenient and useful unit tests we need to slightly refactor the code. Introduce `neoFSAPIServer` interface as a provider of `Client` type's abstraction from the exact NeoFS API server. Add `netMapSnapshot` method for initial implementation. Define core interface provider used in real code. Set `coreServer` as an underlying `neoFSAPIServer` in `Client.Dial`. Cover `Client.NetMapSnapshot` method with unit tests using the opportunity to override the server. From now client library can be tested not only with real physical listeners but with imitations. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
89124d442d
commit
8e3173eacd
5 changed files with 225 additions and 2 deletions
35
client/api.go
Normal file
35
client/api.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
)
|
||||
|
||||
// interface of NeoFS API server. Exists for test purposes only.
|
||||
type neoFSAPIServer interface {
|
||||
netMapSnapshot(context.Context, v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error)
|
||||
}
|
||||
|
||||
// wrapper over real client connection which communicates over NeoFS API protocol.
|
||||
// Provides neoFSAPIServer for Client instances used in real applications.
|
||||
type coreServer client.Client
|
||||
|
||||
// unifies errors of all RPC.
|
||||
func rpcErr(e error) error {
|
||||
return fmt.Errorf("rpc failure: %w", e)
|
||||
}
|
||||
|
||||
// executes NetmapService.NetmapSnapshot RPC declared in NeoFS API protocol
|
||||
// using underlying client.Client.
|
||||
func (x *coreServer) netMapSnapshot(ctx context.Context, req v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error) {
|
||||
resp, err := rpcapi.NetMapSnapshot((*client.Client)(x), &req, client.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, rpcErr(err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -45,6 +45,8 @@ type Client struct {
|
|||
prm PrmInit
|
||||
|
||||
c client.Client
|
||||
|
||||
server neoFSAPIServer
|
||||
}
|
||||
|
||||
// Init brings the Client instance to its initial state.
|
||||
|
@ -95,12 +97,22 @@ func (c *Client) Dial(prm PrmDial) error {
|
|||
client.WithRWTimeout(prm.streamTimeout),
|
||||
)...)
|
||||
|
||||
c.setNeoFSAPIServer((*coreServer)(&c.c))
|
||||
|
||||
// TODO: (neofs-api-go#382) perform generic dial stage of the client.Client
|
||||
_, _ = rpc.Balance(&c.c, new(v2accounting.BalanceRequest))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sets underlying provider of neoFSAPIServer. The method is used for testing as an approach
|
||||
// to skip Dial stage and override NeoFS API server. MUST NOT be used outside test code.
|
||||
// In real applications wrapper over github.com/nspcc-dev/neofs-api-go/v2/rpc/client
|
||||
// is statically used.
|
||||
func (c *Client) setNeoFSAPIServer(server neoFSAPIServer) {
|
||||
c.server = server
|
||||
}
|
||||
|
||||
// Close closes underlying connection to the NeoFS server. Implements io.Closer.
|
||||
// MUST NOT be called before successful Dial. Can be called concurrently
|
||||
// with server operations processing on running goroutines: in this case
|
||||
|
|
39
client/client_test.go
Normal file
39
client/client_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
/*
|
||||
File contains common functionality used for client package testing.
|
||||
*/
|
||||
|
||||
var key, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
||||
var statusErr apistatus.ServerInternal
|
||||
|
||||
func init() {
|
||||
statusErr.SetMessage("test status error")
|
||||
}
|
||||
|
||||
func assertStatusErr(tb testing.TB, res interface{ Status() apistatus.Status }) {
|
||||
require.IsType(tb, &statusErr, res.Status())
|
||||
require.Equal(tb, statusErr.Message(), res.Status().(*apistatus.ServerInternal).Message())
|
||||
}
|
||||
|
||||
func newClient(server neoFSAPIServer) *Client {
|
||||
var prm PrmInit
|
||||
prm.SetDefaultPrivateKey(*key)
|
||||
|
||||
var c Client
|
||||
c.Init(prm)
|
||||
c.setNeoFSAPIServer(server)
|
||||
|
||||
return &c
|
||||
}
|
|
@ -253,9 +253,9 @@ func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResN
|
|||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := rpcapi.NetMapSnapshot(&c.c, &req, client.WithContext(ctx))
|
||||
resp, err := c.server.netMapSnapshot(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc failure: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res ResNetMapSnapshot
|
||||
|
|
137
client/netmap_test.go
Normal file
137
client/netmap_test.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type serverNetMap struct {
|
||||
errTransport error
|
||||
|
||||
signResponse bool
|
||||
|
||||
statusOK bool
|
||||
|
||||
setNetMap bool
|
||||
netMap v2netmap.NetMap
|
||||
}
|
||||
|
||||
func (x *serverNetMap) netMapSnapshot(ctx context.Context, req v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error) {
|
||||
err := signature.VerifyServiceMessage(&req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if x.errTransport != nil {
|
||||
return nil, x.errTransport
|
||||
}
|
||||
|
||||
var body v2netmap.SnapshotResponseBody
|
||||
|
||||
if x.setNetMap {
|
||||
body.SetNetMap(&x.netMap)
|
||||
}
|
||||
|
||||
var meta session.ResponseMetaHeader
|
||||
|
||||
if !x.statusOK {
|
||||
meta.SetStatus(statusErr.ToStatusV2())
|
||||
}
|
||||
|
||||
var resp v2netmap.SnapshotResponse
|
||||
resp.SetBody(&body)
|
||||
resp.SetMetaHeader(&meta)
|
||||
|
||||
if x.signResponse {
|
||||
err = signature.SignServiceMessage(key, &resp)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("sign response: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func TestClient_NetMapSnapshot(t *testing.T) {
|
||||
var err error
|
||||
var prm PrmNetMapSnapshot
|
||||
var res *ResNetMapSnapshot
|
||||
var srv serverNetMap
|
||||
c := newClient(&srv)
|
||||
ctx := context.Background()
|
||||
|
||||
// missing context
|
||||
require.PanicsWithValue(t, panicMsgMissingContext, func() {
|
||||
//nolint:staticcheck
|
||||
_, _ = c.NetMapSnapshot(nil, prm)
|
||||
})
|
||||
|
||||
// request signature
|
||||
srv.errTransport = errors.New("any error")
|
||||
|
||||
_, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.ErrorIs(t, err, srv.errTransport)
|
||||
|
||||
srv.errTransport = nil
|
||||
|
||||
// unsigned response
|
||||
_, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.Error(t, err)
|
||||
|
||||
srv.signResponse = true
|
||||
|
||||
// status failure
|
||||
res, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.NoError(t, err)
|
||||
assertStatusErr(t, res)
|
||||
|
||||
srv.statusOK = true
|
||||
|
||||
// missing netmap field
|
||||
_, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.Error(t, err)
|
||||
|
||||
srv.setNetMap = true
|
||||
|
||||
// invalid network map
|
||||
var netMap netmap.NetMap
|
||||
|
||||
var node netmap.NodeInfo
|
||||
// TODO: #260 use instance corrupter
|
||||
|
||||
var nodeV2 v2netmap.NodeInfo
|
||||
|
||||
node.WriteToV2(&nodeV2)
|
||||
require.Error(t, new(netmap.NodeInfo).ReadFromV2(nodeV2))
|
||||
|
||||
netMap.SetNodes([]netmap.NodeInfo{node})
|
||||
netMap.WriteToV2(&srv.netMap)
|
||||
|
||||
_, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.Error(t, err)
|
||||
|
||||
// correct network map
|
||||
// TODO: #260 use instance normalizer
|
||||
node.SetPublicKey([]byte{1, 2, 3})
|
||||
node.SetNetworkEndpoints("1", "2", "3")
|
||||
|
||||
node.WriteToV2(&nodeV2)
|
||||
require.NoError(t, new(netmap.NodeInfo).ReadFromV2(nodeV2))
|
||||
|
||||
netMap.SetNodes([]netmap.NodeInfo{node})
|
||||
netMap.WriteToV2(&srv.netMap)
|
||||
|
||||
res, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.NoError(t, err)
|
||||
require.True(t, apistatus.IsSuccessful(res.Status()))
|
||||
require.Equal(t, netMap, res.NetMap())
|
||||
}
|
Loading…
Reference in a new issue