mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-12 21:10:36 +00:00
264 lines
11 KiB
Go
264 lines
11 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestWSClientClose(t *testing.T) {
|
|
srv := initTestServer(t, "")
|
|
defer srv.Close()
|
|
wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
wsc.Close()
|
|
}
|
|
|
|
func TestWSClientSubscription(t *testing.T) {
|
|
var cases = map[string]func(*WSClient) (string, error){
|
|
"blocks": (*WSClient).SubscribeForNewBlocks,
|
|
"transactions": func(wsc *WSClient) (string, error) {
|
|
return wsc.SubscribeForNewTransactions(nil)
|
|
},
|
|
"notifications": func(wsc *WSClient) (string, error) {
|
|
return wsc.SubscribeForExecutionNotifications(nil)
|
|
},
|
|
"executions": func(wsc *WSClient) (string, error) {
|
|
return wsc.SubscribeForTransactionExecutions(nil)
|
|
},
|
|
}
|
|
t.Run("good", func(t *testing.T) {
|
|
for name, f := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`)
|
|
defer srv.Close()
|
|
wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
id, err := f(wsc)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "55aaff00", id)
|
|
})
|
|
}
|
|
})
|
|
t.Run("bad", func(t *testing.T) {
|
|
for name, f := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "error":{"code":-32602,"message":"Invalid Params"}}`)
|
|
defer srv.Close()
|
|
wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
_, err = f(wsc)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWSClientUnsubscription(t *testing.T) {
|
|
type responseCheck struct {
|
|
response string
|
|
code func(*testing.T, *WSClient)
|
|
}
|
|
var cases = map[string]responseCheck{
|
|
"good": {`{"jsonrpc": "2.0", "id": 1, "result": true}`, func(t *testing.T, wsc *WSClient) {
|
|
// We can't really subscribe using this stub server, so set up wsc internals.
|
|
wsc.subscriptions["0"] = true
|
|
err := wsc.Unsubscribe("0")
|
|
require.NoError(t, err)
|
|
}},
|
|
"all": {`{"jsonrpc": "2.0", "id": 1, "result": true}`, func(t *testing.T, wsc *WSClient) {
|
|
// We can't really subscribe using this stub server, so set up wsc internals.
|
|
wsc.subscriptions["0"] = true
|
|
err := wsc.UnsubscribeAll()
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(wsc.subscriptions))
|
|
}},
|
|
"not subscribed": {`{"jsonrpc": "2.0", "id": 1, "result": true}`, func(t *testing.T, wsc *WSClient) {
|
|
err := wsc.Unsubscribe("0")
|
|
require.Error(t, err)
|
|
}},
|
|
"error returned": {`{"jsonrpc": "2.0", "id": 1, "error":{"code":-32602,"message":"Invalid Params"}}`, func(t *testing.T, wsc *WSClient) {
|
|
// We can't really subscribe using this stub server, so set up wsc internals.
|
|
wsc.subscriptions["0"] = true
|
|
err := wsc.Unsubscribe("0")
|
|
require.Error(t, err)
|
|
}},
|
|
"false returned": {`{"jsonrpc": "2.0", "id": 1, "result": false}`, func(t *testing.T, wsc *WSClient) {
|
|
// We can't really subscribe using this stub server, so set up wsc internals.
|
|
wsc.subscriptions["0"] = true
|
|
err := wsc.Unsubscribe("0")
|
|
require.Error(t, err)
|
|
}},
|
|
}
|
|
for name, rc := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
srv := initTestServer(t, rc.response)
|
|
defer srv.Close()
|
|
wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
rc.code(t, wsc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWSClientEvents(t *testing.T) {
|
|
var ok bool
|
|
// Events from RPC server test chain.
|
|
var events = []string{
|
|
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7","executions":[{"trigger":"Application","contract":"0x0000000000000000000000000000000000000000","vmstate":"HALT","gas_consumed":"1.048","stack":[{"type":"Integer","value":"1"}],"notifications":[{"contract":"0xc2789e5ab9bab828743833965b1df0d5fbcc206f","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"507574"},{"type":"Array","value":[{"type":"ByteArray","value":"746573746b6579"},{"type":"ByteArray","value":"7465737476616c7565"}]}]}}]}]}]}`,
|
|
`{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0xc2789e5ab9bab828743833965b1df0d5fbcc206f","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"507574"},{"type":"Array","value":[{"type":"ByteArray","value":"746573746b6579"},{"type":"ByteArray","value":"7465737476616c7565"}]}]}}]}`,
|
|
`{"jsonrpc":"2.0","method":"transaction_added","params":[{"txid":"0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7","size":60,"type":"InvocationTransaction","version":1,"attributes":[],"vin":[],"vout":[],"scripts":[],"script":"097465737476616c756507746573746b657952c103507574676f20ccfbd5f01d5b9633387428b8bab95a9e78c2"}]}`,
|
|
`{"jsonrpc":"2.0","method":"block_added","params":[{"hash":"0x48fba8aebf88278818a3dc0caecb230873d1d4ce1ea8bf473634317f94a609e5","version":0,"previousblockhash":"0x33f3e0e24542b2ec3b6420e6881c31f6460a39a4e733d88f7557cbcc3b5ed560","merkleroot":"0x9d922c5cfd4c8cd1da7a6b2265061998dc438bd0dea7145192e2858155e6c57a","time":1586154525,"index":205,"nonce":"0000000000000457","nextconsensus":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","script":{"invocation":"4047a444a51218ac856f1cbc629f251c7c88187910534d6ba87847c86a9a73ed4951d203fd0a87f3e65657a7259269473896841f65c0a0c8efc79d270d917f4ff640435ee2f073c94a02f0276dfe4465037475e44e1c34c0decb87ec9c2f43edf688059fc4366a41c673d72ba772b4782c39e79f01cb981247353216d52d2df1651140527eb0dfd80a800fdd7ac8fbe68fc9366db2d71655d8ba235525a97a69a7181b1e069b82091be711c25e504a17c3c55eee6e76e6af13cb488fbe35d5c5d025c34041f39a02ebe9bb08be0e4aaa890f447dc9453209bbfb4705d8f2d869c2b55ee2d41dbec2ee476a059d77fb7c26400284328d05aece5f3168b48f1db1c6f7be0b","verification":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae"},"tx":[{"txid":"0xf9adfde059810f37b3d0686d67f6b29034e0c669537df7e59b40c14a0508b9ed","size":10,"type":"MinerTransaction","version":0,"attributes":[],"vin":[],"vout":[],"scripts":[]},{"txid":"0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7","size":60,"type":"InvocationTransaction","version":1,"attributes":[],"vin":[],"vout":[],"scripts":[],"script":"097465737476616c756507746573746b657952c103507574676f20ccfbd5f01d5b9633387428b8bab95a9e78c2"}]}]}`,
|
|
`{"jsonrpc":"2.0","method":"event_missed","params":[]}`,
|
|
}
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if req.URL.Path == "/ws" && req.Method == "GET" {
|
|
var upgrader = websocket.Upgrader{}
|
|
ws, err := upgrader.Upgrade(w, req, nil)
|
|
require.NoError(t, err)
|
|
for _, event := range events {
|
|
ws.SetWriteDeadline(time.Now().Add(2 * time.Second))
|
|
err = ws.WriteMessage(1, []byte(event))
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
ws.Close()
|
|
return
|
|
}
|
|
}))
|
|
|
|
wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
for range events {
|
|
select {
|
|
case _, ok = <-wsc.Notifications:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timeout waiting for event")
|
|
}
|
|
require.Equal(t, true, ok)
|
|
}
|
|
select {
|
|
case _, ok = <-wsc.Notifications:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timeout waiting for event")
|
|
}
|
|
// Connection closed by server.
|
|
require.Equal(t, false, ok)
|
|
}
|
|
|
|
func TestWSExecutionVMStateCheck(t *testing.T) {
|
|
// Will answer successfully if request slips through.
|
|
srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`)
|
|
defer srv.Close()
|
|
wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
filter := "NONE"
|
|
_, err = wsc.SubscribeForTransactionExecutions(&filter)
|
|
require.Error(t, err)
|
|
wsc.Close()
|
|
}
|
|
|
|
func TestWSFilteredSubscriptions(t *testing.T) {
|
|
var cases = []struct {
|
|
name string
|
|
clientCode func(*testing.T, *WSClient)
|
|
serverCode func(*testing.T, *request.Params)
|
|
}{
|
|
{"transactions",
|
|
func(t *testing.T, wsc *WSClient) {
|
|
tt := transaction.InvocationType
|
|
_, err := wsc.SubscribeForNewTransactions(&tt)
|
|
require.NoError(t, err)
|
|
},
|
|
func(t *testing.T, p *request.Params) {
|
|
param := p.Value(1)
|
|
require.NotNil(t, param)
|
|
require.Equal(t, request.TxFilterT, param.Type)
|
|
filt, ok := param.Value.(request.TxFilter)
|
|
require.Equal(t, true, ok)
|
|
require.Equal(t, transaction.InvocationType, filt.Type)
|
|
},
|
|
},
|
|
{"notifications",
|
|
func(t *testing.T, wsc *WSClient) {
|
|
contract := util.Uint160{1, 2, 3, 4, 5}
|
|
_, err := wsc.SubscribeForExecutionNotifications(&contract)
|
|
require.NoError(t, err)
|
|
},
|
|
func(t *testing.T, p *request.Params) {
|
|
param := p.Value(1)
|
|
require.NotNil(t, param)
|
|
require.Equal(t, request.NotificationFilterT, param.Type)
|
|
filt, ok := param.Value.(request.NotificationFilter)
|
|
require.Equal(t, true, ok)
|
|
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, filt.Contract)
|
|
},
|
|
},
|
|
{"executions",
|
|
func(t *testing.T, wsc *WSClient) {
|
|
state := "FAULT"
|
|
_, err := wsc.SubscribeForTransactionExecutions(&state)
|
|
require.NoError(t, err)
|
|
},
|
|
func(t *testing.T, p *request.Params) {
|
|
param := p.Value(1)
|
|
require.NotNil(t, param)
|
|
require.Equal(t, request.ExecutionFilterT, param.Type)
|
|
filt, ok := param.Value.(request.ExecutionFilter)
|
|
require.Equal(t, true, ok)
|
|
require.Equal(t, "FAULT", filt.State)
|
|
},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if req.URL.Path == "/ws" && req.Method == "GET" {
|
|
var upgrader = websocket.Upgrader{}
|
|
ws, err := upgrader.Upgrade(w, req, nil)
|
|
require.NoError(t, err)
|
|
ws.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
req := request.In{}
|
|
err = ws.ReadJSON(&req)
|
|
require.NoError(t, err)
|
|
params, err := req.Params()
|
|
require.NoError(t, err)
|
|
c.serverCode(t, params)
|
|
ws.SetWriteDeadline(time.Now().Add(2 * time.Second))
|
|
err = ws.WriteMessage(1, []byte(`{"jsonrpc": "2.0", "id": 1, "result": "0"}`))
|
|
require.NoError(t, err)
|
|
ws.Close()
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
c.clientCode(t, wsc)
|
|
wsc.Close()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewWS(t *testing.T) {
|
|
srv := initTestServer(t, "")
|
|
defer srv.Close()
|
|
|
|
t.Run("good", func(t *testing.T) {
|
|
_, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{})
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("bad URL", func(t *testing.T) {
|
|
_, err := NewWS(context.TODO(), strings.Trim(srv.URL, "http://"), Options{})
|
|
require.Error(t, err)
|
|
})
|
|
}
|