github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/server/parse_test.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"net/http"
     7  	"strconv"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  
    12  	"github.com/ari-anchor/sei-tendermint/libs/bytes"
    13  )
    14  
    15  func TestParseJSONMap(t *testing.T) {
    16  	input := []byte(`{"value":"1234","height":22}`)
    17  
    18  	// naive is float,string
    19  	var p1 map[string]interface{}
    20  	err := json.Unmarshal(input, &p1)
    21  	if assert.NoError(t, err) {
    22  		h, ok := p1["height"].(float64)
    23  		if assert.True(t, ok, "%#v", p1["height"]) {
    24  			assert.EqualValues(t, 22, h)
    25  		}
    26  		v, ok := p1["value"].(string)
    27  		if assert.True(t, ok, "%#v", p1["value"]) {
    28  			assert.EqualValues(t, "1234", v)
    29  		}
    30  	}
    31  
    32  	// preloading map with values doesn't help
    33  	tmp := 0
    34  	p2 := map[string]interface{}{
    35  		"value":  &bytes.HexBytes{},
    36  		"height": &tmp,
    37  	}
    38  	err = json.Unmarshal(input, &p2)
    39  	if assert.NoError(t, err) {
    40  		h, ok := p2["height"].(float64)
    41  		if assert.True(t, ok, "%#v", p2["height"]) {
    42  			assert.EqualValues(t, 22, h)
    43  		}
    44  		v, ok := p2["value"].(string)
    45  		if assert.True(t, ok, "%#v", p2["value"]) {
    46  			assert.EqualValues(t, "1234", v)
    47  		}
    48  	}
    49  
    50  	// preload here with *pointers* to the desired types
    51  	// struct has unknown types, but hard-coded keys
    52  	tmp = 0
    53  	p3 := struct {
    54  		Value  interface{} `json:"value"`
    55  		Height interface{} `json:"height"`
    56  	}{
    57  		Height: &tmp,
    58  		Value:  &bytes.HexBytes{},
    59  	}
    60  	err = json.Unmarshal(input, &p3)
    61  	if assert.NoError(t, err) {
    62  		h, ok := p3.Height.(*int)
    63  		if assert.True(t, ok, "%#v", p3.Height) {
    64  			assert.Equal(t, 22, *h)
    65  		}
    66  		v, ok := p3.Value.(*bytes.HexBytes)
    67  		if assert.True(t, ok, "%#v", p3.Value) {
    68  			assert.EqualValues(t, []byte{0x12, 0x34}, *v)
    69  		}
    70  	}
    71  
    72  	// simplest solution, but hard-coded
    73  	p4 := struct {
    74  		Value  bytes.HexBytes `json:"value"`
    75  		Height int            `json:"height"`
    76  	}{}
    77  	err = json.Unmarshal(input, &p4)
    78  	if assert.NoError(t, err) {
    79  		assert.EqualValues(t, 22, p4.Height)
    80  		assert.EqualValues(t, []byte{0x12, 0x34}, p4.Value)
    81  	}
    82  
    83  	// so, let's use this trick...
    84  	// dynamic keys on map, and we can deserialize to the desired types
    85  	var p5 map[string]*json.RawMessage
    86  	err = json.Unmarshal(input, &p5)
    87  	if assert.NoError(t, err) {
    88  		var h int
    89  		err = json.Unmarshal(*p5["height"], &h)
    90  		if assert.NoError(t, err) {
    91  			assert.Equal(t, 22, h)
    92  		}
    93  
    94  		var v bytes.HexBytes
    95  		err = json.Unmarshal(*p5["value"], &v)
    96  		if assert.NoError(t, err) {
    97  			assert.Equal(t, bytes.HexBytes{0x12, 0x34}, v)
    98  		}
    99  	}
   100  }
   101  
   102  func TestParseJSONArray(t *testing.T) {
   103  	input := []byte(`["1234",22]`)
   104  
   105  	// naive is float,string
   106  	var p1 []interface{}
   107  	err := json.Unmarshal(input, &p1)
   108  	if assert.NoError(t, err) {
   109  		v, ok := p1[0].(string)
   110  		if assert.True(t, ok, "%#v", p1[0]) {
   111  			assert.EqualValues(t, "1234", v)
   112  		}
   113  		h, ok := p1[1].(float64)
   114  		if assert.True(t, ok, "%#v", p1[1]) {
   115  			assert.EqualValues(t, 22, h)
   116  		}
   117  	}
   118  
   119  	// preloading map with values helps here (unlike map - p2 above)
   120  	tmp := 0
   121  	p2 := []interface{}{&bytes.HexBytes{}, &tmp}
   122  	err = json.Unmarshal(input, &p2)
   123  	if assert.NoError(t, err) {
   124  		v, ok := p2[0].(*bytes.HexBytes)
   125  		if assert.True(t, ok, "%#v", p2[0]) {
   126  			assert.EqualValues(t, []byte{0x12, 0x34}, *v)
   127  		}
   128  		h, ok := p2[1].(*int)
   129  		if assert.True(t, ok, "%#v", p2[1]) {
   130  			assert.EqualValues(t, 22, *h)
   131  		}
   132  	}
   133  }
   134  
   135  func TestParseJSONRPC(t *testing.T) {
   136  	type demoArgs struct {
   137  		Height int    `json:"height,string"`
   138  		Name   string `json:"name"`
   139  	}
   140  	demo := func(ctx context.Context, _ *demoArgs) error { return nil }
   141  	rfunc := NewRPCFunc(demo)
   142  
   143  	cases := []struct {
   144  		raw    string
   145  		height int64
   146  		name   string
   147  		fail   bool
   148  	}{
   149  		// should parse
   150  		{`["7", "flew"]`, 7, "flew", false},
   151  		{`{"name": "john", "height": "22"}`, 22, "john", false},
   152  		// defaults
   153  		{`{"name": "solo", "unused": "stuff"}`, 0, "solo", false},
   154  		// should fail - wrong types/length
   155  		{`["flew", 7]`, 0, "", true},
   156  		{`[7,"flew",100]`, 0, "", true},
   157  		{`{"name": -12, "height": "fred"}`, 0, "", true},
   158  	}
   159  	ctx := context.Background()
   160  	for idx, tc := range cases {
   161  		i := strconv.Itoa(idx)
   162  		vals, err := rfunc.parseParams(ctx, []byte(tc.raw))
   163  		if tc.fail {
   164  			assert.Error(t, err, i)
   165  		} else {
   166  			assert.NoError(t, err, "%s: %+v", i, err)
   167  			assert.Equal(t, 2, len(vals), i)
   168  			p, ok := vals[1].Interface().(*demoArgs)
   169  			if assert.True(t, ok) {
   170  				assert.Equal(t, tc.height, int64(p.Height), i)
   171  				assert.Equal(t, tc.name, p.Name, i)
   172  			}
   173  		}
   174  
   175  	}
   176  }
   177  
   178  func TestParseURI(t *testing.T) {
   179  	// URI parameter parsing happens in two phases:
   180  	//
   181  	// Phase 1 swizzles the query parameters into JSON.  The result of this
   182  	// phase must be valid JSON, but may fail the second stage.
   183  	//
   184  	// Phase 2 decodes the JSON to obtain the actual arguments. A failure at
   185  	// this stage means the JSON is not compatible with the target.
   186  
   187  	t.Run("Swizzle", func(t *testing.T) {
   188  		tests := []struct {
   189  			name string
   190  			url  string
   191  			args []argInfo
   192  			want string
   193  			fail bool
   194  		}{
   195  			{
   196  				name: "quoted numbers and strings",
   197  				url:  `http://localhost?num="7"&str="flew"&neg="-10"`,
   198  				args: []argInfo{{name: "neg"}, {name: "num"}, {name: "str"}, {name: "other"}},
   199  				want: `{"neg":-10,"num":7,"str":"flew"}`,
   200  			},
   201  			{
   202  				name: "unquoted numbers and strings",
   203  				url:  `http://localhost?num1=7&str1=cabbage&num2=-199&str2=hey+you`,
   204  				args: []argInfo{{name: "num1"}, {name: "num2"}, {name: "str1"}, {name: "str2"}, {name: "other"}},
   205  				want: `{"num1":7,"num2":-199,"str1":"cabbage","str2":"hey you"}`,
   206  			},
   207  			{
   208  				name: "quoted byte strings",
   209  				url:  `http://localhost?left="Fahrvergnügen"&right="Applesauce"`,
   210  				args: []argInfo{{name: "left", isBinary: true}, {name: "right", isBinary: false}},
   211  				want: `{"left":"RmFocnZlcmduw7xnZW4=","right":"Applesauce"}`,
   212  			},
   213  			{
   214  				name: "hexadecimal byte strings",
   215  				url:  `http://localhost?lower=0x626f62&upper=0X646F7567`,
   216  				args: []argInfo{{name: "upper", isBinary: true}, {name: "lower", isBinary: false}, {name: "other"}},
   217  				want: `{"lower":"bob","upper":"ZG91Zw=="}`,
   218  			},
   219  			{
   220  				name: "invalid hex odd length",
   221  				url:  `http://localhost?bad=0xa`,
   222  				args: []argInfo{{name: "bad"}, {name: "superbad"}},
   223  				fail: true,
   224  			},
   225  			{
   226  				name: "invalid hex empty",
   227  				url:  `http://localhost?bad=0x`,
   228  				args: []argInfo{{name: "bad"}},
   229  				fail: true,
   230  			},
   231  			{
   232  				name: "invalid quoted string",
   233  				url:  `http://localhost?bad="double""`,
   234  				args: []argInfo{{name: "bad"}},
   235  				fail: true,
   236  			},
   237  		}
   238  		for _, test := range tests {
   239  			t.Run(test.name, func(t *testing.T) {
   240  				hreq, err := http.NewRequest("GET", test.url, nil)
   241  				if err != nil {
   242  					t.Fatalf("NewRequest for %q: %v", test.url, err)
   243  				}
   244  
   245  				bits, err := parseURLParams(test.args, hreq)
   246  				if err != nil && !test.fail {
   247  					t.Fatalf("Parse %q: unexpected error: %v", test.url, err)
   248  				} else if err == nil && test.fail {
   249  					t.Fatalf("Parse %q: got %#q, wanted error", test.url, string(bits))
   250  				}
   251  				if got := string(bits); got != test.want {
   252  					t.Errorf("Parse %q: got %#q, want %#q", test.url, got, test.want)
   253  				}
   254  			})
   255  		}
   256  	})
   257  
   258  	t.Run("Decode", func(t *testing.T) {
   259  		type argValue struct {
   260  			Height json.Number `json:"height"`
   261  			Name   string      `json:"name"`
   262  			Flag   bool        `json:"flag"`
   263  		}
   264  
   265  		echo := NewRPCFunc(func(_ context.Context, arg *argValue) (*argValue, error) {
   266  			return arg, nil
   267  		})
   268  
   269  		tests := []struct {
   270  			name string
   271  			url  string
   272  			fail string
   273  			want interface{}
   274  		}{
   275  			{
   276  				name: "valid all args",
   277  				url:  `http://localhost?height=235&flag=true&name="bogart"`,
   278  				want: &argValue{
   279  					Height: "235",
   280  					Flag:   true,
   281  					Name:   "bogart",
   282  				},
   283  			},
   284  			{
   285  				name: "valid partial args",
   286  				url:  `http://localhost?height="1987"&name=free+willy`,
   287  				want: &argValue{
   288  					Height: "1987",
   289  					Name:   "free willy",
   290  				},
   291  			},
   292  			{
   293  				name: "invalid quoted number",
   294  				url:  `http://localhost?height="-xx"`,
   295  				fail: "invalid number literal",
   296  			},
   297  			{
   298  				name: "invalid unquoted number",
   299  				url:  `http://localhost?height=25*q`,
   300  				fail: "invalid number literal",
   301  			},
   302  			{
   303  				name: "invalid boolean",
   304  				url:  `http://localhost?flag="garbage"`,
   305  				fail: "flag of type bool",
   306  			},
   307  		}
   308  		for _, test := range tests {
   309  			t.Run(test.name, func(t *testing.T) {
   310  				hreq, err := http.NewRequest("GET", test.url, nil)
   311  				if err != nil {
   312  					t.Fatalf("NewRequest for %q: %v", test.url, err)
   313  				}
   314  				bits, err := parseURLParams(echo.args, hreq)
   315  				if err != nil {
   316  					t.Fatalf("Parse %#q: unexpected error: %v", test.url, err)
   317  				}
   318  				rsp, err := echo.Call(context.Background(), bits)
   319  				if test.want != nil {
   320  					assert.Equal(t, test.want, rsp)
   321  				}
   322  				if test.fail != "" {
   323  					assert.ErrorContains(t, err, test.fail)
   324  				}
   325  			})
   326  		}
   327  	})
   328  }