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  }