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