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