You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
5.0 KiB

  1. package rpcserver_test
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io/ioutil"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. "github.com/gorilla/websocket"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. amino "github.com/tendermint/go-amino"
  14. rs "github.com/tendermint/tendermint/rpc/lib/server"
  15. types "github.com/tendermint/tendermint/rpc/lib/types"
  16. "github.com/tendermint/tendermint/libs/log"
  17. )
  18. //////////////////////////////////////////////////////////////////////////////
  19. // HTTP REST API
  20. // TODO
  21. //////////////////////////////////////////////////////////////////////////////
  22. // JSON-RPC over HTTP
  23. func testMux() *http.ServeMux {
  24. funcMap := map[string]*rs.RPCFunc{
  25. "c": rs.NewRPCFunc(func(s string, i int) (string, error) { return "foo", nil }, "s,i"),
  26. }
  27. cdc := amino.NewCodec()
  28. mux := http.NewServeMux()
  29. buf := new(bytes.Buffer)
  30. logger := log.NewTMLogger(buf)
  31. rs.RegisterRPCFuncs(mux, funcMap, cdc, logger)
  32. return mux
  33. }
  34. func statusOK(code int) bool { return code >= 200 && code <= 299 }
  35. // Ensure that nefarious/unintended inputs to `params`
  36. // do not crash our RPC handlers.
  37. // See Issue https://github.com/tendermint/tendermint/issues/708.
  38. func TestRPCParams(t *testing.T) {
  39. mux := testMux()
  40. tests := []struct {
  41. payload string
  42. wantErr string
  43. }{
  44. // bad
  45. {`{"jsonrpc": "2.0", "id": "0"}`, "Method not found"},
  46. {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found"},
  47. {`{"method": "c", "id": "0", "params": a}`, "invalid character"},
  48. {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1"},
  49. {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character"},
  50. {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string"},
  51. // good
  52. {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, ""},
  53. {`{"method": "c", "id": "0", "params": {}}`, ""},
  54. {`{"method": "c", "id": "0", "params": ["a", "10"]}`, ""},
  55. }
  56. for i, tt := range tests {
  57. req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
  58. rec := httptest.NewRecorder()
  59. mux.ServeHTTP(rec, req)
  60. res := rec.Result()
  61. // Always expecting back a JSONRPCResponse
  62. assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
  63. blob, err := ioutil.ReadAll(res.Body)
  64. if err != nil {
  65. t.Errorf("#%d: err reading body: %v", i, err)
  66. continue
  67. }
  68. recv := new(types.RPCResponse)
  69. assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
  70. assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
  71. if tt.wantErr == "" {
  72. assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
  73. } else {
  74. assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
  75. // The wanted error is either in the message or the data
  76. assert.Contains(t, recv.Error.Message+recv.Error.Data, tt.wantErr, "#%d: expected substring", i)
  77. }
  78. }
  79. }
  80. func TestRPCNotification(t *testing.T) {
  81. mux := testMux()
  82. body := strings.NewReader(`{"jsonrpc": "2.0"}`)
  83. req, _ := http.NewRequest("POST", "http://localhost/", body)
  84. rec := httptest.NewRecorder()
  85. mux.ServeHTTP(rec, req)
  86. res := rec.Result()
  87. // Always expecting back a JSONRPCResponse
  88. require.True(t, statusOK(res.StatusCode), "should always return 2XX")
  89. blob, err := ioutil.ReadAll(res.Body)
  90. require.Nil(t, err, "reading from the body should not give back an error")
  91. require.Equal(t, len(blob), 0, "a notification SHOULD NOT be responded to by the server")
  92. }
  93. func TestUnknownRPCPath(t *testing.T) {
  94. mux := testMux()
  95. req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil)
  96. rec := httptest.NewRecorder()
  97. mux.ServeHTTP(rec, req)
  98. res := rec.Result()
  99. // Always expecting back a 404 error
  100. require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404")
  101. }
  102. //////////////////////////////////////////////////////////////////////////////
  103. // JSON-RPC over WEBSOCKETS
  104. func TestWebsocketManagerHandler(t *testing.T) {
  105. s := newWSServer()
  106. defer s.Close()
  107. // check upgrader works
  108. d := websocket.Dialer{}
  109. c, dialResp, err := d.Dial("ws://"+s.Listener.Addr().String()+"/websocket", nil)
  110. require.NoError(t, err)
  111. if got, want := dialResp.StatusCode, http.StatusSwitchingProtocols; got != want {
  112. t.Errorf("dialResp.StatusCode = %q, want %q", got, want)
  113. }
  114. // check basic functionality works
  115. req, err := types.MapToRequest(amino.NewCodec(), "TestWebsocketManager", "c", map[string]interface{}{"s": "a", "i": 10})
  116. require.NoError(t, err)
  117. err = c.WriteJSON(req)
  118. require.NoError(t, err)
  119. var resp types.RPCResponse
  120. err = c.ReadJSON(&resp)
  121. require.NoError(t, err)
  122. require.Nil(t, resp.Error)
  123. }
  124. func newWSServer() *httptest.Server {
  125. funcMap := map[string]*rs.RPCFunc{
  126. "c": rs.NewWSRPCFunc(func(wsCtx types.WSRPCContext, s string, i int) (string, error) { return "foo", nil }, "s,i"),
  127. }
  128. wm := rs.NewWebsocketManager(funcMap, amino.NewCodec())
  129. wm.SetLogger(log.TestingLogger())
  130. mux := http.NewServeMux()
  131. mux.HandleFunc("/websocket", wm.WebsocketHandler)
  132. return httptest.NewServer(mux)
  133. }