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  }