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