@ -0,0 +1,186 @@ | |||
package mock | |||
import ( | |||
abci "github.com/tendermint/abci/types" | |||
"github.com/tendermint/tendermint/rpc/client" | |||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// ABCIApp will send all abci related request to the named app, | |||
// so you can test app behavior from a client without needing | |||
// an entire tendermint node | |||
type ABCIApp struct { | |||
App abci.Application | |||
} | |||
func (a ABCIApp) _assertABCIClient() client.ABCIClient { | |||
return a | |||
} | |||
func (a ABCIApp) ABCIInfo() (*ctypes.ResultABCIInfo, error) { | |||
return &ctypes.ResultABCIInfo{a.App.Info()}, nil | |||
} | |||
func (a ABCIApp) ABCIQuery(path string, data []byte, prove bool) (*ctypes.ResultABCIQuery, error) { | |||
q := a.App.Query(abci.RequestQuery{data, path, 0, prove}) | |||
return &ctypes.ResultABCIQuery{q}, nil | |||
} | |||
func (a ABCIApp) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { | |||
res := ctypes.ResultBroadcastTxCommit{} | |||
c := a.App.CheckTx(tx) | |||
res.CheckTx = &abci.ResponseCheckTx{c.Code, c.Data, c.Log} | |||
if !c.IsOK() { | |||
return &res, nil | |||
} | |||
d := a.App.DeliverTx(tx) | |||
res.DeliverTx = &abci.ResponseDeliverTx{d.Code, d.Data, d.Log} | |||
return &res, nil | |||
} | |||
func (a ABCIApp) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
d := a.App.DeliverTx(tx) | |||
return &ctypes.ResultBroadcastTx{d.Code, d.Data, d.Log}, nil | |||
} | |||
func (a ABCIApp) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
d := a.App.DeliverTx(tx) | |||
return &ctypes.ResultBroadcastTx{d.Code, d.Data, d.Log}, nil | |||
} | |||
// ABCIMock will send all abci related request to the named app, | |||
// so you can test app behavior from a client without needing | |||
// an entire tendermint node | |||
type ABCIMock struct { | |||
Info Call | |||
Query Call | |||
BroadcastCommit Call | |||
Broadcast Call | |||
} | |||
func (m ABCIMock) _assertABCIClient() client.ABCIClient { | |||
return m | |||
} | |||
func (m ABCIMock) ABCIInfo() (*ctypes.ResultABCIInfo, error) { | |||
res, err := m.Info.GetResponse(nil) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &ctypes.ResultABCIInfo{res.(abci.ResponseInfo)}, nil | |||
} | |||
func (m ABCIMock) ABCIQuery(path string, data []byte, prove bool) (*ctypes.ResultABCIQuery, error) { | |||
res, err := m.Query.GetResponse(QueryArgs{path, data, prove}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &ctypes.ResultABCIQuery{res.(abci.ResponseQuery)}, nil | |||
} | |||
func (m ABCIMock) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { | |||
res, err := m.BroadcastCommit.GetResponse(tx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return res.(*ctypes.ResultBroadcastTxCommit), nil | |||
} | |||
func (m ABCIMock) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
res, err := m.Broadcast.GetResponse(tx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return res.(*ctypes.ResultBroadcastTx), nil | |||
} | |||
func (m ABCIMock) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
res, err := m.Broadcast.GetResponse(tx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return res.(*ctypes.ResultBroadcastTx), nil | |||
} | |||
// ABCIRecorder can wrap another type (ABCIApp, ABCIMock, or Client) | |||
// and record all ABCI related calls. | |||
type ABCIRecorder struct { | |||
Client client.ABCIClient | |||
Calls []Call | |||
} | |||
func NewABCIRecorder(client client.ABCIClient) *ABCIRecorder { | |||
return &ABCIRecorder{ | |||
Client: client, | |||
Calls: []Call{}, | |||
} | |||
} | |||
func (r *ABCIRecorder) _assertABCIClient() client.ABCIClient { | |||
return r | |||
} | |||
type QueryArgs struct { | |||
Path string | |||
Data []byte | |||
Prove bool | |||
} | |||
func (r *ABCIRecorder) addCall(call Call) { | |||
r.Calls = append(r.Calls, call) | |||
} | |||
func (r *ABCIRecorder) ABCIInfo() (*ctypes.ResultABCIInfo, error) { | |||
res, err := r.Client.ABCIInfo() | |||
r.addCall(Call{ | |||
Name: "abci_info", | |||
Response: res, | |||
Error: err, | |||
}) | |||
return res, err | |||
} | |||
func (r *ABCIRecorder) ABCIQuery(path string, data []byte, prove bool) (*ctypes.ResultABCIQuery, error) { | |||
res, err := r.Client.ABCIQuery(path, data, prove) | |||
r.addCall(Call{ | |||
Name: "abci_query", | |||
Args: QueryArgs{path, data, prove}, | |||
Response: res, | |||
Error: err, | |||
}) | |||
return res, err | |||
} | |||
func (r *ABCIRecorder) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { | |||
res, err := r.Client.BroadcastTxCommit(tx) | |||
r.addCall(Call{ | |||
Name: "broadcast_tx_commit", | |||
Args: tx, | |||
Response: res, | |||
Error: err, | |||
}) | |||
return res, err | |||
} | |||
func (r *ABCIRecorder) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
res, err := r.Client.BroadcastTxAsync(tx) | |||
r.addCall(Call{ | |||
Name: "broadcast_tx_async", | |||
Args: tx, | |||
Response: res, | |||
Error: err, | |||
}) | |||
return res, err | |||
} | |||
func (r *ABCIRecorder) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
res, err := r.Client.BroadcastTxSync(tx) | |||
r.addCall(Call{ | |||
Name: "broadcast_tx_sync", | |||
Args: tx, | |||
Response: res, | |||
Error: err, | |||
}) | |||
return res, err | |||
} |
@ -0,0 +1,142 @@ | |||
package mock_test | |||
import ( | |||
"errors" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
abci "github.com/tendermint/abci/types" | |||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||
"github.com/tendermint/tendermint/types" | |||
"github.com/tendermint/tendermint/rpc/client/mock" | |||
) | |||
func TestABCIMock(t *testing.T) { | |||
assert, require := assert.New(t), require.New(t) | |||
key, value := []byte("foo"), []byte("bar") | |||
height := uint64(10) | |||
goodTx := types.Tx{0x01, 0xff} | |||
badTx := types.Tx{0x12, 0x21} | |||
m := mock.ABCIMock{ | |||
Info: mock.Call{Error: errors.New("foobar")}, | |||
Query: mock.Call{Response: abci.ResponseQuery{ | |||
Key: key, | |||
Value: value, | |||
Height: height, | |||
}}, | |||
// Broadcast commit depends on call | |||
BroadcastCommit: mock.Call{ | |||
Args: goodTx, | |||
Response: &ctypes.ResultBroadcastTxCommit{ | |||
CheckTx: &abci.ResponseCheckTx{Data: []byte("stand")}, | |||
DeliverTx: &abci.ResponseDeliverTx{Data: []byte("deliver")}, | |||
}, | |||
Error: errors.New("bad tx"), | |||
}, | |||
Broadcast: mock.Call{Error: errors.New("must commit")}, | |||
} | |||
// now, let's try to make some calls | |||
_, err := m.ABCIInfo() | |||
require.NotNil(err) | |||
assert.Equal("foobar", err.Error()) | |||
// query always returns the response | |||
query, err := m.ABCIQuery("/", nil, false) | |||
require.Nil(err) | |||
require.NotNil(query) | |||
assert.Equal(key, query.Response.GetKey()) | |||
assert.Equal(value, query.Response.GetValue()) | |||
assert.Equal(height, query.Response.GetHeight()) | |||
// non-commit calls always return errors | |||
_, err = m.BroadcastTxSync(goodTx) | |||
require.NotNil(err) | |||
assert.Equal("must commit", err.Error()) | |||
_, err = m.BroadcastTxAsync(goodTx) | |||
require.NotNil(err) | |||
assert.Equal("must commit", err.Error()) | |||
// commit depends on the input | |||
_, err = m.BroadcastTxCommit(badTx) | |||
require.NotNil(err) | |||
assert.Equal("bad tx", err.Error()) | |||
bres, err := m.BroadcastTxCommit(goodTx) | |||
require.Nil(err, "%+v", err) | |||
assert.EqualValues(0, bres.CheckTx.Code) | |||
assert.EqualValues("stand", bres.CheckTx.Data) | |||
assert.EqualValues("deliver", bres.DeliverTx.Data) | |||
} | |||
func TestABCIRecorder(t *testing.T) { | |||
assert, require := assert.New(t), require.New(t) | |||
m := mock.ABCIMock{ | |||
Info: mock.Call{Response: abci.ResponseInfo{ | |||
Data: "data", | |||
Version: "v0.9.9", | |||
}}, | |||
Query: mock.Call{Error: errors.New("query")}, | |||
Broadcast: mock.Call{Error: errors.New("broadcast")}, | |||
BroadcastCommit: mock.Call{Error: errors.New("broadcast_commit")}, | |||
} | |||
r := mock.NewABCIRecorder(m) | |||
require.Equal(0, len(r.Calls)) | |||
r.ABCIInfo() | |||
r.ABCIQuery("path", []byte("data"), true) | |||
require.Equal(2, len(r.Calls)) | |||
info := r.Calls[0] | |||
assert.Equal("abci_info", info.Name) | |||
assert.Nil(info.Error) | |||
assert.Nil(info.Args) | |||
require.NotNil(info.Response) | |||
ir, ok := info.Response.(*ctypes.ResultABCIInfo) | |||
require.True(ok) | |||
assert.Equal("data", ir.Response.Data) | |||
assert.Equal("v0.9.9", ir.Response.Version) | |||
query := r.Calls[1] | |||
assert.Equal("abci_query", query.Name) | |||
assert.Nil(query.Response) | |||
require.NotNil(query.Error) | |||
assert.Equal("query", query.Error.Error()) | |||
require.NotNil(query.Args) | |||
qa, ok := query.Args.(mock.QueryArgs) | |||
require.True(ok) | |||
assert.Equal("path", qa.Path) | |||
assert.EqualValues("data", qa.Data) | |||
assert.True(qa.Prove) | |||
// now add some broadcasts | |||
txs := []types.Tx{{1}, {2}, {3}} | |||
r.BroadcastTxCommit(txs[0]) | |||
r.BroadcastTxSync(txs[1]) | |||
r.BroadcastTxAsync(txs[2]) | |||
require.Equal(5, len(r.Calls)) | |||
bc := r.Calls[2] | |||
assert.Equal("broadcast_tx_commit", bc.Name) | |||
assert.Nil(bc.Response) | |||
require.NotNil(bc.Error) | |||
assert.EqualValues(bc.Args, txs[0]) | |||
bs := r.Calls[3] | |||
assert.Equal("broadcast_tx_sync", bs.Name) | |||
assert.Nil(bs.Response) | |||
require.NotNil(bs.Error) | |||
assert.EqualValues(bs.Args, txs[1]) | |||
ba := r.Calls[4] | |||
assert.Equal("broadcast_tx_async", ba.Name) | |||
assert.Nil(ba.Response) | |||
require.NotNil(ba.Error) | |||
assert.EqualValues(ba.Args, txs[2]) | |||
} |
@ -0,0 +1,169 @@ | |||
/* | |||
package mock returns a Client implementation that | |||
accepts various (mock) implementations of the various methods. | |||
This implementation is useful for using in tests, when you don't | |||
need a real server, but want a high-level of control about | |||
the server response you want to mock (eg. error handling), | |||
or if you just want to record the calls to verify in your tests. | |||
For real clients, you probably want the "http" package. If you | |||
want to directly call a tendermint node in process, you can use the | |||
"local" package. | |||
*/ | |||
package mock | |||
import ( | |||
"reflect" | |||
"github.com/tendermint/tendermint/rpc/client" | |||
"github.com/tendermint/tendermint/rpc/core" | |||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// Client wraps arbitrary implementations of the various interfaces. | |||
// | |||
// We provide a few choices to mock out each one in this package. | |||
// Nothing hidden here, so no New function, just construct it from | |||
// some parts, and swap them out them during the tests. | |||
type Client struct { | |||
client.ABCIClient | |||
client.SignClient | |||
client.HistoryClient | |||
client.StatusClient | |||
} | |||
func (c Client) _assertIsClient() client.Client { | |||
return c | |||
} | |||
// Call is used by recorders to save a call and response. | |||
// It can also be used to configure mock responses. | |||
// | |||
type Call struct { | |||
Name string | |||
Args interface{} | |||
Response interface{} | |||
Error error | |||
} | |||
// GetResponse will generate the apporiate response for us, when | |||
// using the Call struct to configure a Mock handler. | |||
// | |||
// When configuring a response, if only one of Response or Error is | |||
// set then that will always be returned. If both are set, then | |||
// we return Response if the Args match the set args, Error otherwise. | |||
func (c Call) GetResponse(args interface{}) (interface{}, error) { | |||
// handle the case with no response | |||
if c.Response == nil { | |||
if c.Error == nil { | |||
panic("Misconfigured call, you must set either Response or Error") | |||
} | |||
return nil, c.Error | |||
} | |||
// response without error | |||
if c.Error == nil { | |||
return c.Response, nil | |||
} | |||
// have both, we must check args.... | |||
if reflect.DeepEqual(args, c.Args) { | |||
return c.Response, nil | |||
} | |||
return nil, c.Error | |||
} | |||
func (c Client) Status() (*ctypes.ResultStatus, error) { | |||
return core.Status() | |||
} | |||
func (c Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) { | |||
return core.ABCIInfo() | |||
} | |||
func (c Client) ABCIQuery(path string, data []byte, prove bool) (*ctypes.ResultABCIQuery, error) { | |||
return core.ABCIQuery(path, data, prove) | |||
} | |||
func (c Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { | |||
return core.BroadcastTxCommit(tx) | |||
} | |||
func (c Client) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
return core.BroadcastTxAsync(tx) | |||
} | |||
func (c Client) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | |||
return core.BroadcastTxSync(tx) | |||
} | |||
func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { | |||
return core.NetInfo() | |||
} | |||
func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { | |||
return core.UnsafeDialSeeds(seeds) | |||
} | |||
func (c Client) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) { | |||
return core.BlockchainInfo(minHeight, maxHeight) | |||
} | |||
func (c Client) Genesis() (*ctypes.ResultGenesis, error) { | |||
return core.Genesis() | |||
} | |||
func (c Client) Block(height int) (*ctypes.ResultBlock, error) { | |||
return core.Block(height) | |||
} | |||
func (c Client) Commit(height int) (*ctypes.ResultCommit, error) { | |||
return core.Commit(height) | |||
} | |||
func (c Client) Validators() (*ctypes.ResultValidators, error) { | |||
return core.Validators() | |||
} | |||
/** websocket event stuff here... **/ | |||
/* | |||
// StartWebsocket starts up a websocket and a listener goroutine | |||
// if already started, do nothing | |||
func (c Client) StartWebsocket() error { | |||
var err error | |||
if c.ws == nil { | |||
ws := rpcclient.NewWSClient(c.remote, c.endpoint) | |||
_, err = ws.Start() | |||
if err == nil { | |||
c.ws = ws | |||
} | |||
} | |||
return errors.Wrap(err, "StartWebsocket") | |||
} | |||
// StopWebsocket stops the websocket connection | |||
func (c Client) StopWebsocket() { | |||
if c.ws != nil { | |||
c.ws.Stop() | |||
c.ws = nil | |||
} | |||
} | |||
// GetEventChannels returns the results and error channel from the websocket | |||
func (c Client) GetEventChannels() (chan json.RawMessage, chan error) { | |||
if c.ws == nil { | |||
return nil, nil | |||
} | |||
return c.ws.ResultsCh, c.ws.ErrorsCh | |||
} | |||
func (c Client) Subscribe(event string) error { | |||
return errors.Wrap(c.ws.Subscribe(event), "Subscribe") | |||
} | |||
func (c Client) Unsubscribe(event string) error { | |||
return errors.Wrap(c.ws.Unsubscribe(event), "Unsubscribe") | |||
} | |||
*/ |