diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server_test.go index 6c8f4e622..1e4e3ed6a 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server_test.go @@ -7,175 +7,335 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "reflect" "strings" "testing" + "github.com/CityOfZion/neo-go/pkg/core" + "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" + "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +type executor struct { + chain *core.Blockchain + handler http.HandlerFunc +} + +const ( + defaultJSONRPC = "2.0" + defaultID = 1 +) + +type rpcTestCase struct { + name string + params string + fail bool + result func(e *executor) interface{} + check func(t *testing.T, e *executor, result interface{}) +} + +var rpcTestCases = map[string][]rpcTestCase{ + "getaccountstate": { + { + name: "positive", + params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, + result: func(e *executor) interface{} { return &GetAccountStateResponse{} }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*GetAccountStateResponse) + require.True(t, ok) + assert.Equal(t, 1, len(res.Result.Balances)) + assert.Equal(t, false, res.Result.Frozen) + }, + }, + { + name: "negative", + params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, + result: func(e *executor) interface{} { return "Invalid public account address" }, + }, + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid address", + params: `["notabase58"]`, + fail: true, + }, + }, + "getassetstate": { + { + name: "positive", + params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]`, + result: func(e *executor) interface{} { return &GetAssetResponse{} }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*GetAssetResponse) + require.True(t, ok) + assert.Equal(t, "00", res.Result.Owner) + assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Result.Admin) + }, + }, + { + name: "negative", + params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de2"]`, + result: func(e *executor) interface{} { return "Invalid assetid" }, + }, + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid hash", + params: `["notahex"]`, + fail: true, + }, + }, + "getbestblockhash": { + { + params: "[]", + result: func(e *executor) interface{} { + return "0x" + e.chain.CurrentBlockHash().ReverseString() + }, + }, + { + params: "1", + fail: true, + }, + }, + "getblock": { + { + name: "positive", + params: "[1]", + result: func(e *executor) interface{} { return &GetBlockResponse{} }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*GetBlockResponse) + require.True(t, ok) + + block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) + require.NoErrorf(t, err, "could not get block") + + expectedHash := "0x" + block.Hash().ReverseString() + assert.Equal(t, expectedHash, res.Result.Hash) + }, + }, + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid height", + params: `[-1]`, + fail: true, + }, + { + name: "invalid hash", + params: `["notahex"]`, + fail: true, + }, + { + name: "missing hash", + params: `["` + util.Uint256{}.String() + `"]`, + fail: true, + }, + }, + "getblockcount": { + { + params: "[]", + result: func(e *executor) interface{} { return int(e.chain.BlockHeight() + 1) }, + }, + }, + "getblockhash": { + { + params: "[1]", + result: func(e *executor) interface{} { return "" }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*StringResultResponse) + require.True(t, ok) + + block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) + require.NoErrorf(t, err, "could not get block") + + expectedHash := "0x" + block.Hash().ReverseString() + assert.Equal(t, expectedHash, res.Result) + }, + }, + { + name: "string height", + params: `["first"]`, + fail: true, + }, + { + name: "invalid number height", + params: `[-2]`, + fail: true, + }, + }, + "getconnectioncount": { + { + params: "[]", + result: func(*executor) interface{} { return 0 }, + }, + }, + "getpeers": { + { + params: "[]", + result: func(*executor) interface{} { + return &GetPeersResponse{ + Jsonrpc: defaultJSONRPC, + Result: struct { + Unconnected []int `json:"unconnected"` + Connected []int `json:"connected"` + Bad []int `json:"bad"` + }{ + Unconnected: []int{}, + Connected: []int{}, + Bad: []int{}, + }, + ID: defaultID, + } + }, + }, + }, + "getrawtransaction": { + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid hash", + params: `["notahex"]`, + fail: true, + }, + { + name: "missing hash", + params: `["` + util.Uint256{}.String() + `"]`, + fail: true, + }, + }, + "getunspents": { + { + name: "positive", + params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, + result: func(e *executor) interface{} { return &GetUnspents{} }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*GetUnspents) + require.True(t, ok) + require.Equal(t, 1, len(res.Result.Balance)) + assert.Equal(t, 1, len(res.Result.Balance[0].Unspents)) + }, + }, + { + name: "negative", + params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, + result: func(e *executor) interface{} { return "Invalid public account address" }, + }, + }, + "getversion": { + { + params: "[]", + result: func(*executor) interface{} { return &GetVersionResponse{} }, + check: func(t *testing.T, e *executor, result interface{}) { + resp, ok := result.(*GetVersionResponse) + require.True(t, ok) + require.Equal(t, "/NEO-GO:/", resp.Result.UserAgent) + }, + }, + }, + "sendrawtransaction": { + { + name: "positive", + params: `["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`, + result: func(e *executor) interface{} { return &SendTXResponse{} }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*SendTXResponse) + require.True(t, ok) + assert.True(t, res.Result) + }, + }, + { + name: "negative", + params: `["0274d792072617720636f6e7472616374207472616e73616374696f6e206465736372697074696f6e01949354ea0a8b57dfee1e257a1aedd1e0eea2e5837de145e8da9c0f101bfccc8e0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500a3e11100000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5004f2418010000001cc9c05cefffe6cdd7b182816a9152ec218d2ec0014140dbd3cddac5cb2bd9bf6d93701f1a6f1c9dbe2d1b480c54628bbb2a4d536158c747a6af82698edf9f8af1cac3850bcb772bd9c8e4ac38f80704751cc4e0bd0e67232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`, + fail: true, + }, + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid string", + params: `["notahex"]`, + fail: true, + }, + { + name: "invalid tx", + params: `["0274d792072617720636f6e747261637"]`, + fail: true, + }, + }, + "validateaddress": { + { + name: "positive", + params: `["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]`, + result: func(*executor) interface{} { return &ValidateAddrResponse{} }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*ValidateAddrResponse) + require.True(t, ok) + assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Result.Address) + assert.True(t, res.Result.IsValid) + }, + }, + { + name: "negative", + params: "[1]", + result: func(*executor) interface{} { + return &ValidateAddrResponse{ + Jsonrpc: defaultJSONRPC, + Result: wrappers.ValidateAddressResponse{ + Address: float64(1), + IsValid: false, + }, + ID: defaultID, + } + }, + }, + }, +} + func TestRPC(t *testing.T) { chain, handler := initServerWithInMemoryChain(t) - t.Run("getbestblockhash", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getbestblockhash", "params": []}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "0x"+chain.CurrentBlockHash().ReverseString(), res.Result) - }) + e := &executor{chain: chain, handler: handler} + for method, cases := range rpcTestCases { + t.Run(method, func(t *testing.T) { + rpc := `{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}` - t.Run("getblock", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getblock", "params": [1]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res GetBlockResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - block, err := chain.GetBlock(chain.GetHeaderHash(1)) - assert.NoErrorf(t, err, "could not get block") - expectedHash := "0x" + block.Hash().ReverseString() - assert.Equal(t, expectedHash, res.Result.Hash) - }) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), handler, t) + checkErrResponse(t, body, tc.fail) + if tc.fail { + return + } - t.Run("getblockcount", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getblockcount", "params": []}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res IntResultResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, chain.BlockHeight()+1, uint32(res.Result)) - }) + expected, res := tc.getResultPair(e) + err := json.Unmarshal(body, res) + require.NoErrorf(t, err, "could not parse response: %s", body) - t.Run("getblockhash", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getblockhash", "params": [1]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - block, err := chain.GetBlock(chain.GetHeaderHash(1)) - assert.NoErrorf(t, err, "could not get block") - expectedHash := "0x" + block.Hash().ReverseString() - assert.Equal(t, expectedHash, res.Result) - }) - - t.Run("getconnectioncount", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getconnectioncount", "params": []}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res IntResultResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, 0, res.Result) - }) - - t.Run("getversion", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getversion", "params": []}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res GetVersionResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "/NEO-GO:/", res.Result.UserAgent) - }) - - t.Run("getpeers", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getpeers", "params": []}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res GetPeersResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, []int{}, res.Result.Bad) - assert.Equal(t, []int{}, res.Result.Unconnected) - assert.Equal(t, []int{}, res.Result.Connected) - }) - - t.Run("validateaddress_positive", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": ["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res ValidateAddrResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, true, res.Result.IsValid) - }) - - t.Run("validateaddress_negative", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": [1]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res ValidateAddrResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, false, res.Result.IsValid) - }) - - t.Run("getassetstate_positive", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res GetAssetResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "00", res.Result.Owner) - assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Result.Admin) - }) - - t.Run("getassetstate_negative", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de2"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "Invalid assetid", res.Result) - }) - - t.Run("getaccountstate_positive", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res GetAccountStateResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, 1, len(res.Result.Balances)) - assert.Equal(t, false, res.Result.Frozen) - }) - - t.Run("getunspents_positive", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getunspents", "params": ["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res GetUnspents - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, 1, len(res.Result.Balance)) - assert.Equal(t, 1, len(res.Result.Balance[0].Unspents)) - }) - - t.Run("getaccountstate_negative", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "Invalid public account address", res.Result) - }) - - t.Run("getunspents_negative", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getunspents", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "Invalid public account address", res.Result) - }) + if tc.check == nil { + assert.Equal(t, expected, res) + } else { + tc.check(t, e, res) + } + }) + } + }) + } t.Run("getrawtransaction", func(t *testing.T) { block, _ := chain.GetBlock(chain.GetHeaderHash(0)) @@ -185,31 +345,52 @@ func TestRPC(t *testing.T) { checkErrResponse(t, body, false) var res StringResultResponse err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) + require.NoErrorf(t, err, "could not parse response: %s", body) assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) }) - t.Run("sendrawtransaction_positive", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]}` + t.Run("getrawtransaction 2 arguments", func(t *testing.T) { + block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + TXHash := block.Transactions[1].Hash() + rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, TXHash.ReverseString()) body := doRPCCall(rpc, handler, t) checkErrResponse(t, body, false) - var res SendTXResponse + var res StringResultResponse err := json.Unmarshal(body, &res) - assert.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, true, res.Result) + require.NoErrorf(t, err, "could not parse response: %s", body) + assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) }) +} - t.Run("sendrawtransaction_negative", func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["0274d792072617720636f6e7472616374207472616e73616374696f6e206465736372697074696f6e01949354ea0a8b57dfee1e257a1aedd1e0eea2e5837de145e8da9c0f101bfccc8e0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500a3e11100000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5004f2418010000001cc9c05cefffe6cdd7b182816a9152ec218d2ec0014140dbd3cddac5cb2bd9bf6d93701f1a6f1c9dbe2d1b480c54628bbb2a4d536158c747a6af82698edf9f8af1cac3850bcb772bd9c8e4ac38f80704751cc4e0bd0e67232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]}` - body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, true) - }) +func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) { + expected = tc.result(e) + switch exp := expected.(type) { + case string: + res = new(StringResultResponse) + expected = &StringResultResponse{ + Jsonrpc: defaultJSONRPC, + Result: exp, + ID: defaultID, + } + case int: + res = new(IntResultResponse) + expected = &IntResultResponse{ + Jsonrpc: defaultJSONRPC, + Result: exp, + ID: defaultID, + } + default: + resVal := reflect.New(reflect.TypeOf(expected).Elem()) + res = resVal.Interface() + } + + return } func checkErrResponse(t *testing.T, body []byte, expectingFail bool) { var errresp ErrorResponse err := json.Unmarshal(body, &errresp) - assert.Nil(t, err) + require.Nil(t, err) if expectingFail { assert.NotEqual(t, 0, errresp.Error.Code) assert.NotEqual(t, "", errresp.Error.Message)