github.com/okex/exchain@v1.8.0/libs/tendermint/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  	"strings"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	amino "github.com/tendermint/go-amino"
    16  
    17  	"github.com/okex/exchain/libs/tendermint/libs/log"
    18  	types "github.com/okex/exchain/libs/tendermint/rpc/jsonrpc/types"
    19  )
    20  
    21  func testMux() *http.ServeMux {
    22  	funcMap := map[string]*RPCFunc{
    23  		"c": NewRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i"),
    24  	}
    25  	cdc := amino.NewCodec()
    26  	mux := http.NewServeMux()
    27  	buf := new(bytes.Buffer)
    28  	logger := log.NewTMLogger(buf)
    29  	RegisterRPCFuncs(mux, funcMap, cdc, logger)
    30  
    31  	return mux
    32  }
    33  
    34  func statusOK(code int) bool { return code >= 200 && code <= 299 }
    35  
    36  // Ensure that nefarious/unintended inputs to `params`
    37  // do not crash our RPC handlers.
    38  // See Issue https://github.com/tendermint/tendermint/issues/708.
    39  func TestRPCParams(t *testing.T) {
    40  	mux := testMux()
    41  	tests := []struct {
    42  		payload    string
    43  		wantErr    string
    44  		expectedID interface{}
    45  	}{
    46  		// bad
    47  		{`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
    48  		{`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
    49  		// id not captured in JSON parsing failures
    50  		{`{"method": "c", "id": "0", "params": a}`, "invalid character", nil},
    51  		{`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")},
    52  		{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")},
    53  		{`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")},
    54  
    55  		// no ID - notification
    56  		// {`{"jsonrpc": "2.0", "method": "c", "params": ["a", "10"]}`, false, nil},
    57  
    58  		// good
    59  		{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")},
    60  		{`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")},
    61  		{`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")},
    62  	}
    63  
    64  	for i, tt := range tests {
    65  		req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
    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.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
    72  		blob, err := ioutil.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  		rec := httptest.NewRecorder()
   115  		mux.ServeHTTP(rec, req)
   116  		res := rec.Result()
   117  		// Always expecting back a JSONRPCResponse
   118  		assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
   119  		blob, err := ioutil.ReadAll(res.Body)
   120  		if err != nil {
   121  			t.Errorf("#%d: err reading body: %v", i, err)
   122  			continue
   123  		}
   124  		res.Body.Close()
   125  
   126  		recv := new(types.RPCResponse)
   127  		err = json.Unmarshal(blob, recv)
   128  		assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
   129  		if !tt.wantErr {
   130  			assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
   131  			assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
   132  			assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
   133  		} else {
   134  			assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
   135  		}
   136  	}
   137  }
   138  
   139  func TestRPCNotification(t *testing.T) {
   140  	mux := testMux()
   141  	body := strings.NewReader(`{"jsonrpc": "2.0"}`)
   142  	req, _ := http.NewRequest("POST", "http://localhost/", body)
   143  	rec := httptest.NewRecorder()
   144  	mux.ServeHTTP(rec, req)
   145  	res := rec.Result()
   146  
   147  	// Always expecting back a JSONRPCResponse
   148  	require.True(t, statusOK(res.StatusCode), "should always return 2XX")
   149  	blob, err := ioutil.ReadAll(res.Body)
   150  	res.Body.Close()
   151  	require.Nil(t, err, "reading from the body should not give back an error")
   152  	require.Equal(t, len(blob), 0, "a notification SHOULD NOT be responded to by the server")
   153  }
   154  
   155  func TestRPCNotificationInBatch(t *testing.T) {
   156  	mux := testMux()
   157  	tests := []struct {
   158  		payload     string
   159  		expectCount int
   160  	}{
   161  		{
   162  			`[
   163  				{"jsonrpc": "2.0"},
   164  				{"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]}
   165  			 ]`,
   166  			1,
   167  		},
   168  		{
   169  			`[
   170  				{"jsonrpc": "2.0"},
   171  				{"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]},
   172  				{"jsonrpc": "2.0"},
   173  				{"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]}
   174  			 ]`,
   175  			2,
   176  		},
   177  	}
   178  	for i, tt := range tests {
   179  		req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
   180  		rec := httptest.NewRecorder()
   181  		mux.ServeHTTP(rec, req)
   182  		res := rec.Result()
   183  		// Always expecting back a JSONRPCResponse
   184  		assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
   185  		blob, err := ioutil.ReadAll(res.Body)
   186  		if err != nil {
   187  			t.Errorf("#%d: err reading body: %v", i, err)
   188  			continue
   189  		}
   190  		res.Body.Close()
   191  
   192  		var responses []types.RPCResponse
   193  		// try to unmarshal an array first
   194  		err = json.Unmarshal(blob, &responses)
   195  		if err != nil {
   196  			// if we were actually expecting an array, but got an error
   197  			if tt.expectCount > 1 {
   198  				t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob)
   199  				continue
   200  			} else {
   201  				// we were expecting an error here, so let's unmarshal a single response
   202  				var response types.RPCResponse
   203  				err = json.Unmarshal(blob, &response)
   204  				if err != nil {
   205  					t.Errorf("#%d: expected successful parsing of an RPCResponse\nblob: %s", i, blob)
   206  					continue
   207  				}
   208  				// have a single-element result
   209  				responses = []types.RPCResponse{response}
   210  			}
   211  		}
   212  		if tt.expectCount != len(responses) {
   213  			t.Errorf("#%d: expected %d response(s), but got %d\nblob: %s", i, tt.expectCount, len(responses), blob)
   214  			continue
   215  		}
   216  		for _, response := range responses {
   217  			assert.NotEqual(t, response, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
   218  		}
   219  	}
   220  }
   221  
   222  func TestUnknownRPCPath(t *testing.T) {
   223  	mux := testMux()
   224  	req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil)
   225  	rec := httptest.NewRecorder()
   226  	mux.ServeHTTP(rec, req)
   227  	res := rec.Result()
   228  
   229  	// Always expecting back a 404 error
   230  	require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404")
   231  	res.Body.Close()
   232  }