github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/rpc/jsonrpc/server/http_json_handler_test.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "strings" 10 "testing" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "github.com/tendermint/tendermint/libs/log" 16 types "github.com/tendermint/tendermint/rpc/jsonrpc/types" 17 ) 18 19 func testMux() *http.ServeMux { 20 funcMap := map[string]*RPCFunc{ 21 "c": NewRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i"), 22 } 23 mux := http.NewServeMux() 24 buf := new(bytes.Buffer) 25 logger := log.NewTMLogger(buf) 26 RegisterRPCFuncs(mux, funcMap, logger) 27 28 return mux 29 } 30 31 func statusOK(code int) bool { return code >= 200 && code <= 299 } 32 33 // Ensure that nefarious/unintended inputs to `params` 34 // do not crash our RPC handlers. 35 // See Issue https://github.com/tendermint/tendermint/issues/708. 36 func TestRPCParams(t *testing.T) { 37 mux := testMux() 38 tests := []struct { 39 payload string 40 wantErr string 41 expectedID interface{} 42 }{ 43 // bad 44 {`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")}, 45 {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")}, 46 // id not captured in JSON parsing failures 47 {`{"method": "c", "id": "0", "params": a}`, "invalid character", nil}, 48 {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")}, 49 {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")}, 50 {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")}, 51 52 // no ID - notification 53 // {`{"jsonrpc": "2.0", "method": "c", "params": ["a", "10"]}`, false, nil}, 54 55 // good 56 {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")}, 57 {`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")}, 58 {`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")}, 59 } 60 61 for i, tt := range tests { 62 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 63 rec := httptest.NewRecorder() 64 mux.ServeHTTP(rec, req) 65 res := rec.Result() 66 defer res.Body.Close() 67 // Always expecting back a JSONRPCResponse 68 assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) 69 blob, err := ioutil.ReadAll(res.Body) 70 if err != nil { 71 t.Errorf("#%d: err reading body: %v", i, err) 72 continue 73 } 74 75 recv := new(types.RPCResponse) 76 assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) 77 assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 78 assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i) 79 if tt.wantErr == "" { 80 assert.Nil(t, recv.Error, "#%d: not expecting an error", i) 81 } else { 82 assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i) 83 // The wanted error is either in the message or the data 84 assert.Contains(t, recv.Error.Message+recv.Error.Data, tt.wantErr, "#%d: expected substring", i) 85 } 86 } 87 } 88 89 func TestJSONRPCID(t *testing.T) { 90 mux := testMux() 91 tests := []struct { 92 payload string 93 wantErr bool 94 expectedID interface{} 95 }{ 96 // good id 97 {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")}, 98 {`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")}, 99 {`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)}, 100 {`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, 101 {`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, 102 {`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)}, 103 104 // bad id 105 {`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil}, 106 {`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil}, 107 } 108 109 for i, tt := range tests { 110 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 111 rec := httptest.NewRecorder() 112 mux.ServeHTTP(rec, req) 113 res := rec.Result() 114 // Always expecting back a JSONRPCResponse 115 assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) 116 blob, err := ioutil.ReadAll(res.Body) 117 if err != nil { 118 t.Errorf("#%d: err reading body: %v", i, err) 119 continue 120 } 121 res.Body.Close() 122 123 recv := new(types.RPCResponse) 124 err = json.Unmarshal(blob, recv) 125 assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) 126 if !tt.wantErr { 127 assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 128 assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i) 129 assert.Nil(t, recv.Error, "#%d: not expecting an error", i) 130 } else { 131 assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i) 132 } 133 } 134 } 135 136 func TestRPCNotification(t *testing.T) { 137 mux := testMux() 138 body := strings.NewReader(`{"jsonrpc": "2.0"}`) 139 req, _ := http.NewRequest("POST", "http://localhost/", body) 140 rec := httptest.NewRecorder() 141 mux.ServeHTTP(rec, req) 142 res := rec.Result() 143 144 // Always expecting back a JSONRPCResponse 145 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 146 blob, err := ioutil.ReadAll(res.Body) 147 res.Body.Close() 148 require.Nil(t, err, "reading from the body should not give back an error") 149 require.Equal(t, len(blob), 0, "a notification SHOULD NOT be responded to by the server") 150 } 151 152 func TestRPCNotificationInBatch(t *testing.T) { 153 mux := testMux() 154 tests := []struct { 155 payload string 156 expectCount int 157 }{ 158 { 159 `[ 160 {"jsonrpc": "2.0"}, 161 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} 162 ]`, 163 1, 164 }, 165 { 166 `[ 167 {"jsonrpc": "2.0"}, 168 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]}, 169 {"jsonrpc": "2.0"}, 170 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} 171 ]`, 172 2, 173 }, 174 } 175 for i, tt := range tests { 176 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 177 rec := httptest.NewRecorder() 178 mux.ServeHTTP(rec, req) 179 res := rec.Result() 180 // Always expecting back a JSONRPCResponse 181 assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) 182 blob, err := ioutil.ReadAll(res.Body) 183 if err != nil { 184 t.Errorf("#%d: err reading body: %v", i, err) 185 continue 186 } 187 res.Body.Close() 188 189 var responses []types.RPCResponse 190 // try to unmarshal an array first 191 err = json.Unmarshal(blob, &responses) 192 if err != nil { 193 // if we were actually expecting an array, but got an error 194 if tt.expectCount > 1 { 195 t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob) 196 continue 197 } else { 198 // we were expecting an error here, so let's unmarshal a single response 199 var response types.RPCResponse 200 err = json.Unmarshal(blob, &response) 201 if err != nil { 202 t.Errorf("#%d: expected successful parsing of an RPCResponse\nblob: %s", i, blob) 203 continue 204 } 205 // have a single-element result 206 responses = []types.RPCResponse{response} 207 } 208 } 209 if tt.expectCount != len(responses) { 210 t.Errorf("#%d: expected %d response(s), but got %d\nblob: %s", i, tt.expectCount, len(responses), blob) 211 continue 212 } 213 for _, response := range responses { 214 assert.NotEqual(t, response, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 215 } 216 } 217 } 218 219 func TestUnknownRPCPath(t *testing.T) { 220 mux := testMux() 221 req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil) 222 rec := httptest.NewRecorder() 223 mux.ServeHTTP(rec, req) 224 res := rec.Result() 225 226 // Always expecting back a 404 error 227 require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404") 228 res.Body.Close() 229 }