github.com/Finschia/ostracon@v1.1.5/rpc/jsonrpc/server/http_json_handler_test.go (about)

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     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/Finschia/ostracon/libs/log"
    17  	types "github.com/Finschia/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  		"block": NewRPCFunc(func(ctx *types.Context, h int) (string, error) { return "block", nil }, "height", Cacheable("height")),
    24  	}
    25  	mux := http.NewServeMux()
    26  	buf := new(bytes.Buffer)
    27  	logger := log.NewOCLogger(buf)
    28  	RegisterRPCFuncs(mux, funcMap, logger)
    29  
    30  	return mux
    31  }
    32  
    33  func statusOK(code int) bool { return code >= 200 && code <= 299 }
    34  
    35  // Ensure that nefarious/unintended inputs to `params`
    36  // do not crash our RPC handlers.
    37  // See Issue https://github.com/tendermint/tendermint/issues/708.
    38  func TestRPCParams(t *testing.T) {
    39  	mux := testMux()
    40  	tests := []struct {
    41  		payload    string
    42  		wantErr    string
    43  		expectedID interface{}
    44  	}{
    45  		// bad
    46  		{`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
    47  		{`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
    48  		// id not captured in JSON parsing failures
    49  		{`{"method": "c", "id": "0", "params": a}`, "invalid character", nil},
    50  		{`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")},
    51  		{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")},
    52  		{`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")},
    53  
    54  		// no ID - notification
    55  		// {`{"jsonrpc": "2.0", "method": "c", "params": ["a", "10"]}`, false, nil},
    56  
    57  		// good
    58  		{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")},
    59  		{`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")},
    60  		{`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")},
    61  	}
    62  
    63  	for i, tt := range tests {
    64  		req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
    65  		req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
    66  		rec := httptest.NewRecorder()
    67  		mux.ServeHTTP(rec, req)
    68  		res := rec.Result()
    69  		defer res.Body.Close()
    70  		// Always expecting back a JSONRPCResponse
    71  		assert.NotZero(t, res.StatusCode, "#%d: should always return code", i)
    72  		blob, err := io.ReadAll(res.Body)
    73  		if err != nil {
    74  			t.Errorf("#%d: err reading body: %v", i, err)
    75  			continue
    76  		}
    77  
    78  		recv := new(types.RPCResponse)
    79  		assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
    80  		assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
    81  		assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
    82  		if tt.wantErr == "" {
    83  			assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
    84  		} else {
    85  			assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
    86  			// The wanted error is either in the message or the data
    87  			assert.Contains(t, recv.Error.Message+recv.Error.Data, tt.wantErr, "#%d: expected substring", i)
    88  		}
    89  	}
    90  }
    91  
    92  func TestJSONRPCID(t *testing.T) {
    93  	mux := testMux()
    94  	tests := []struct {
    95  		payload    string
    96  		wantErr    bool
    97  		expectedID interface{}
    98  	}{
    99  		// good id
   100  		{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")},
   101  		{`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")},
   102  		{`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)},
   103  		{`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
   104  		{`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
   105  		{`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)},
   106  
   107  		// bad id
   108  		{`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil},
   109  		{`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil},
   110  	}
   111  
   112  	for i, tt := range tests {
   113  		req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
   114  		req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   115  		rec := httptest.NewRecorder()
   116  		mux.ServeHTTP(rec, req)
   117  		res := rec.Result()
   118  		// Always expecting back a JSONRPCResponse
   119  		assert.NotZero(t, res.StatusCode, "#%d: should always return code", i)
   120  		blob, err := io.ReadAll(res.Body)
   121  		if err != nil {
   122  			t.Errorf("#%d: err reading body: %v", i, err)
   123  			continue
   124  		}
   125  		res.Body.Close()
   126  
   127  		recv := new(types.RPCResponse)
   128  		err = json.Unmarshal(blob, recv)
   129  		assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
   130  		if !tt.wantErr {
   131  			assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
   132  			assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
   133  			assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
   134  		} else {
   135  			assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
   136  		}
   137  	}
   138  }
   139  
   140  func TestRPCNotification(t *testing.T) {
   141  	mux := testMux()
   142  	body := strings.NewReader(`{"jsonrpc": "2.0"}`)
   143  	req, _ := http.NewRequest("POST", "http://localhost/", body)
   144  	req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   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.Nil(t, err, "reading from the body should not give back an error")
   154  	require.Equal(t, 0, len(blob), "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  		req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   183  		rec := httptest.NewRecorder()
   184  		mux.ServeHTTP(rec, req)
   185  		res := rec.Result()
   186  		// Always expecting back a JSONRPCResponse
   187  		assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
   188  		blob, err := io.ReadAll(res.Body)
   189  		if err != nil {
   190  			t.Errorf("#%d: err reading body: %v", i, err)
   191  			continue
   192  		}
   193  		res.Body.Close()
   194  
   195  		var responses []types.RPCResponse
   196  		// try to unmarshal an array first
   197  		err = json.Unmarshal(blob, &responses)
   198  		if err != nil {
   199  			// if we were actually expecting an array, but got an error
   200  			if tt.expectCount > 1 {
   201  				t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob)
   202  				continue
   203  			} else {
   204  				// we were expecting an error here, so let's unmarshal a single response
   205  				var response types.RPCResponse
   206  				err = json.Unmarshal(blob, &response)
   207  				if err != nil {
   208  					t.Errorf("#%d: expected successful parsing of an RPCResponse\nblob: %s", i, blob)
   209  					continue
   210  				}
   211  				// have a single-element result
   212  				responses = []types.RPCResponse{response}
   213  			}
   214  		}
   215  		if tt.expectCount != len(responses) {
   216  			t.Errorf("#%d: expected %d response(s), but got %d\nblob: %s", i, tt.expectCount, len(responses), blob)
   217  			continue
   218  		}
   219  		for _, response := range responses {
   220  			assert.NotEqual(t, response, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
   221  		}
   222  	}
   223  }
   224  
   225  func TestTooManyRPCNotificationInBatch_error(t *testing.T) {
   226  	// prepare the mock batch request
   227  	var jsonArray []json.RawMessage
   228  	for i := 0; i < 11; i++ {
   229  		jsonArray = append(jsonArray, json.RawMessage(TestGoodBody))
   230  	}
   231  	jsonData, err := json.Marshal(jsonArray)
   232  	if err != nil {
   233  		t.Errorf("expected an array, couldn't marshal it")
   234  	}
   235  	// execute the batch request
   236  	mux := testMux()
   237  	req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(string(jsonData)))
   238  	req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   239  	rec := httptest.NewRecorder()
   240  	mux.ServeHTTP(rec, req)
   241  	res := rec.Result()
   242  	res.Body.Close()
   243  	// always expecting back a 400 error
   244  	assert.Equal(t, http.StatusBadRequest, res.StatusCode, "should always return 400")
   245  }
   246  
   247  func TestNoMaxBatchRequestNumField_error(t *testing.T) {
   248  	// execute the batch request
   249  	mux := testMux()
   250  	req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(TestGoodBody))
   251  	rec := httptest.NewRecorder()
   252  	mux.ServeHTTP(rec, req)
   253  	res := rec.Result()
   254  	res.Body.Close()
   255  	// always expecting back a 500 error
   256  	assert.Equal(t, http.StatusInternalServerError, res.StatusCode, "should always return 500")
   257  }
   258  
   259  func TestUnknownRPCPath(t *testing.T) {
   260  	mux := testMux()
   261  	req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil)
   262  	rec := httptest.NewRecorder()
   263  	mux.ServeHTTP(rec, req)
   264  	res := rec.Result()
   265  
   266  	// Always expecting back a 404 error
   267  	require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404")
   268  	res.Body.Close()
   269  }
   270  
   271  func TestRPCResponseCache(t *testing.T) {
   272  	mux := testMux()
   273  	body := strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["1"]}`)
   274  	req, _ := http.NewRequest("Get", "http://localhost/", body)
   275  	req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   276  	rec := httptest.NewRecorder()
   277  	mux.ServeHTTP(rec, req)
   278  	res := rec.Result()
   279  
   280  	// Always expecting back a JSONRPCResponse
   281  	require.True(t, statusOK(res.StatusCode), "should always return 2XX")
   282  	require.Equal(t, "public, max-age=86400", res.Header.Get("Cache-control"))
   283  
   284  	_, err := io.ReadAll(res.Body)
   285  	res.Body.Close()
   286  	require.Nil(t, err, "reading from the body should not give back an error")
   287  
   288  	// send a request with default height.
   289  	body = strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["0"]}`)
   290  	req, _ = http.NewRequest("Get", "http://localhost/", body)
   291  	req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   292  	rec = httptest.NewRecorder()
   293  	mux.ServeHTTP(rec, req)
   294  	res = rec.Result()
   295  
   296  	// Always expecting back a JSONRPCResponse
   297  	require.True(t, statusOK(res.StatusCode), "should always return 2XX")
   298  	require.Equal(t, "", res.Header.Get("Cache-control"))
   299  
   300  	_, err = io.ReadAll(res.Body)
   301  
   302  	res.Body.Close()
   303  	require.Nil(t, err, "reading from the body should not give back an error")
   304  
   305  	// send a request with default height, but as empty set of parameters.
   306  	body = strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": []}`)
   307  	req, _ = http.NewRequest("Get", "http://localhost/", body)
   308  	req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   309  	rec = httptest.NewRecorder()
   310  	mux.ServeHTTP(rec, req)
   311  	res = rec.Result()
   312  
   313  	// Always expecting back a JSONRPCResponse
   314  	require.True(t, statusOK(res.StatusCode), "should always return 2XX")
   315  	require.Equal(t, "", res.Header.Get("Cache-control"))
   316  
   317  	_, err = io.ReadAll(res.Body)
   318  
   319  	res.Body.Close()
   320  	require.Nil(t, err, "reading from the body should not give back an error")
   321  }
   322  
   323  func TestMakeJSONRPCHandler_Unmarshal_WriteRPCResponseHTTPError_error(t *testing.T) {
   324  	handlerFunc := makeJSONRPCHandler(nil, log.TestingLogger())
   325  	// json.Unmarshal error
   326  	req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader("hoge"))
   327  	// WriteRPCResponseHTTPError error
   328  	rec := NewFailedWriteResponseWriter()
   329  	handlerFunc.ServeHTTP(rec, req)
   330  	assert.Equal(t,
   331  		strconv.Itoa(http.StatusInternalServerError),
   332  		rec.Header().Get(http.StatusText(http.StatusInternalServerError)))
   333  }
   334  
   335  func TestMakeJSONRPCHandler_last_WriteRPCResponseHTTP_error(t *testing.T) {
   336  	handlerFunc := makeJSONRPCHandler(TestFuncMap, log.TestingLogger())
   337  	req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody))
   338  	req.Header.Set("Max-Batch-Request-Num", TestMaxBatchRequestNum)
   339  	// WriteRPCResponseHTTP error
   340  	rec := NewFailedWriteResponseWriter()
   341  	handlerFunc.ServeHTTP(rec, req)
   342  	assert.Equal(t,
   343  		strconv.Itoa(http.StatusOK),
   344  		rec.Header().Get(http.StatusText(http.StatusOK)))
   345  }