github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/lib/server/parse_test.go (about)

     1  package rpcserver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  
    12  	types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types"
    13  )
    14  
    15  func TestParseJSONMap(t *testing.T) {
    16  	t.Parallel()
    17  
    18  	input := []byte(`{"value":"1234","height":22}`)
    19  
    20  	// naive is float,string
    21  	var p1 map[string]interface{}
    22  	err := json.Unmarshal(input, &p1)
    23  	if assert.Nil(t, err) {
    24  		h, ok := p1["height"].(float64)
    25  		if assert.True(t, ok, "%#v", p1["height"]) {
    26  			assert.EqualValues(t, 22, h)
    27  		}
    28  		v, ok := p1["value"].(string)
    29  		if assert.True(t, ok, "%#v", p1["value"]) {
    30  			assert.EqualValues(t, "1234", v)
    31  		}
    32  	}
    33  
    34  	// preloading map with values doesn't help
    35  	tmp := 0
    36  	p2 := map[string]interface{}{
    37  		"value":  &[]byte{},
    38  		"height": &tmp,
    39  	}
    40  	err = json.Unmarshal(input, &p2)
    41  	if assert.Nil(t, err) {
    42  		h, ok := p2["height"].(float64)
    43  		if assert.True(t, ok, "%#v", p2["height"]) {
    44  			assert.EqualValues(t, 22, h)
    45  		}
    46  		v, ok := p2["value"].(string)
    47  		if assert.True(t, ok, "%#v", p2["value"]) {
    48  			assert.EqualValues(t, "1234", v)
    49  		}
    50  	}
    51  
    52  	// preload here with *pointers* to the desired types
    53  	// struct has unknown types, but hard-coded keys
    54  	tmp = 0
    55  	p3 := struct {
    56  		Value  interface{} `json:"value"`
    57  		Height interface{} `json:"height"`
    58  	}{
    59  		Height: &tmp,
    60  		Value:  &[]byte{},
    61  	}
    62  	err = json.Unmarshal(input, &p3)
    63  	if assert.Nil(t, err) {
    64  		h, ok := p3.Height.(*int)
    65  		if assert.True(t, ok, "%#v", p3.Height) {
    66  			assert.Equal(t, 22, *h)
    67  		}
    68  		v, ok := p3.Value.(*[]byte)
    69  		if assert.True(t, ok, "%#v", p3.Value) {
    70  			// "1234" is interpreted as base64, decodes to the following bytes.
    71  			assert.EqualValues(t, []byte{0xd7, 0x6d, 0xf8}, *v)
    72  		}
    73  	}
    74  
    75  	// simplest solution, but hard-coded
    76  	p4 := struct {
    77  		Value  []byte `json:"value"`
    78  		Height int    `json:"height"`
    79  	}{}
    80  	err = json.Unmarshal(input, &p4)
    81  	if assert.Nil(t, err) {
    82  		assert.EqualValues(t, 22, p4.Height)
    83  		assert.EqualValues(t, []byte{0xd7, 0x6d, 0xf8}, p4.Value)
    84  	}
    85  
    86  	// so, let's use this trick...
    87  	// dynamic keys on map, and we can deserialize to the desired types
    88  	var p5 map[string]*json.RawMessage
    89  	err = json.Unmarshal(input, &p5)
    90  	if assert.Nil(t, err) {
    91  		var h int
    92  		err = json.Unmarshal(*p5["height"], &h)
    93  		if assert.Nil(t, err) {
    94  			assert.Equal(t, 22, h)
    95  		}
    96  
    97  		var v []byte
    98  		err = json.Unmarshal(*p5["value"], &v)
    99  		if assert.Nil(t, err) {
   100  			assert.Equal(t, []byte{0xd7, 0x6d, 0xf8}, v)
   101  		}
   102  	}
   103  }
   104  
   105  func TestParseJSONArray(t *testing.T) {
   106  	t.Parallel()
   107  
   108  	input := []byte(`["1234",22]`)
   109  
   110  	// naive is float,string
   111  	var p1 []interface{}
   112  	err := json.Unmarshal(input, &p1)
   113  	if assert.Nil(t, err) {
   114  		v, ok := p1[0].(string)
   115  		if assert.True(t, ok, "%#v", p1[0]) {
   116  			assert.EqualValues(t, "1234", v)
   117  		}
   118  		h, ok := p1[1].(float64)
   119  		if assert.True(t, ok, "%#v", p1[1]) {
   120  			assert.EqualValues(t, 22, h)
   121  		}
   122  	}
   123  
   124  	// preloading map with values helps here (unlike map - p2 above)
   125  	tmp := 0
   126  	p2 := []interface{}{&[]byte{}, &tmp}
   127  	err = json.Unmarshal(input, &p2)
   128  	if assert.Nil(t, err) {
   129  		v, ok := p2[0].(*[]byte)
   130  		if assert.True(t, ok, "%#v", p2[0]) {
   131  			assert.EqualValues(t, []byte{0xd7, 0x6d, 0xf8}, *v)
   132  		}
   133  		h, ok := p2[1].(*int)
   134  		if assert.True(t, ok, "%#v", p2[1]) {
   135  			assert.EqualValues(t, 22, *h)
   136  		}
   137  	}
   138  }
   139  
   140  func TestParseJSONRPC(t *testing.T) {
   141  	t.Parallel()
   142  
   143  	demo := func(ctx *types.Context, height int, name string) {}
   144  	call := NewRPCFunc(demo, "height,name")
   145  
   146  	cases := []struct {
   147  		raw    string
   148  		height int64
   149  		name   string
   150  		fail   bool
   151  	}{
   152  		// should parse
   153  		{`["7", "flew"]`, 7, "flew", false},
   154  		{`{"name": "john", "height": "22"}`, 22, "john", false},
   155  		// defaults
   156  		{`{"name": "solo", "unused": "stuff"}`, 0, "solo", false},
   157  		// should fail - wrong types/length
   158  		{`["flew", 7]`, 0, "", true},
   159  		{`[7,"flew",100]`, 0, "", true},
   160  		{`{"name": -12, "height": "fred"}`, 0, "", true},
   161  	}
   162  	for idx, tc := range cases {
   163  		i := strconv.Itoa(idx)
   164  		data := []byte(tc.raw)
   165  		vals, err := jsonParamsToArgs(call, data)
   166  		if tc.fail {
   167  			assert.NotNil(t, err, i)
   168  		} else {
   169  			assert.Nil(t, err, "%s: %+v", i, err)
   170  			if assert.Equal(t, 2, len(vals), i) {
   171  				assert.Equal(t, tc.height, vals[0].Int(), i)
   172  				assert.Equal(t, tc.name, vals[1].String(), i)
   173  			}
   174  		}
   175  	}
   176  }
   177  
   178  func TestParseURI(t *testing.T) {
   179  	t.Parallel()
   180  
   181  	demo := func(ctx *types.Context, height int, name string) {}
   182  	call := NewRPCFunc(demo, "height,name")
   183  
   184  	cases := []struct {
   185  		raw    []string
   186  		height int64
   187  		name   string
   188  		fail   bool
   189  	}{
   190  		// can parse numbers unquoted and strings quoted
   191  		{[]string{"7", `"flew"`}, 7, "flew", false},
   192  		{[]string{"22", `"john"`}, 22, "john", false},
   193  		{[]string{"-10", `"bob"`}, -10, "bob", false},
   194  		// can parse numbers quoted, too
   195  		{[]string{`"7"`, `"flew"`}, 7, "flew", false},
   196  		{[]string{`"-10"`, `"bob"`}, -10, "bob", false},
   197  		// cant parse strings uquoted
   198  		{[]string{`"-10"`, `bob`}, -10, "bob", true},
   199  	}
   200  	for idx, tc := range cases {
   201  		i := strconv.Itoa(idx)
   202  		// data := []byte(tc.raw)
   203  		url := fmt.Sprintf(
   204  			"test.com/method?height=%v&name=%v",
   205  			tc.raw[0], tc.raw[1])
   206  		req, err := http.NewRequest("GET", url, nil)
   207  		assert.NoError(t, err)
   208  		vals, err := httpParamsToArgs(call, req)
   209  		if tc.fail {
   210  			assert.NotNil(t, err, i)
   211  		} else {
   212  			assert.Nil(t, err, "%s: %+v", i, err)
   213  			if assert.Equal(t, 2, len(vals), i) {
   214  				assert.Equal(t, tc.height, vals[0].Int(), i)
   215  				assert.Equal(t, tc.name, vals[1].String(), i)
   216  			}
   217  		}
   218  	}
   219  }