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