mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-07 04:20:44 +00:00
Require that RPC functions take a context as their first argument, and return an error as either their only result, or the second of two results. This does not change how functions are dispatched, but will make it a little easier to make more invasive changes in the near future.
214 lines
5.3 KiB
Go
214 lines
5.3 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/tendermint/tendermint/libs/bytes"
|
|
)
|
|
|
|
func TestParseJSONMap(t *testing.T) {
|
|
input := []byte(`{"value":"1234","height":22}`)
|
|
|
|
// naive is float,string
|
|
var p1 map[string]interface{}
|
|
err := json.Unmarshal(input, &p1)
|
|
if assert.NoError(t, err) {
|
|
h, ok := p1["height"].(float64)
|
|
if assert.True(t, ok, "%#v", p1["height"]) {
|
|
assert.EqualValues(t, 22, h)
|
|
}
|
|
v, ok := p1["value"].(string)
|
|
if assert.True(t, ok, "%#v", p1["value"]) {
|
|
assert.EqualValues(t, "1234", v)
|
|
}
|
|
}
|
|
|
|
// preloading map with values doesn't help
|
|
tmp := 0
|
|
p2 := map[string]interface{}{
|
|
"value": &bytes.HexBytes{},
|
|
"height": &tmp,
|
|
}
|
|
err = json.Unmarshal(input, &p2)
|
|
if assert.NoError(t, err) {
|
|
h, ok := p2["height"].(float64)
|
|
if assert.True(t, ok, "%#v", p2["height"]) {
|
|
assert.EqualValues(t, 22, h)
|
|
}
|
|
v, ok := p2["value"].(string)
|
|
if assert.True(t, ok, "%#v", p2["value"]) {
|
|
assert.EqualValues(t, "1234", v)
|
|
}
|
|
}
|
|
|
|
// preload here with *pointers* to the desired types
|
|
// struct has unknown types, but hard-coded keys
|
|
tmp = 0
|
|
p3 := struct {
|
|
Value interface{} `json:"value"`
|
|
Height interface{} `json:"height"`
|
|
}{
|
|
Height: &tmp,
|
|
Value: &bytes.HexBytes{},
|
|
}
|
|
err = json.Unmarshal(input, &p3)
|
|
if assert.NoError(t, err) {
|
|
h, ok := p3.Height.(*int)
|
|
if assert.True(t, ok, "%#v", p3.Height) {
|
|
assert.Equal(t, 22, *h)
|
|
}
|
|
v, ok := p3.Value.(*bytes.HexBytes)
|
|
if assert.True(t, ok, "%#v", p3.Value) {
|
|
assert.EqualValues(t, []byte{0x12, 0x34}, *v)
|
|
}
|
|
}
|
|
|
|
// simplest solution, but hard-coded
|
|
p4 := struct {
|
|
Value bytes.HexBytes `json:"value"`
|
|
Height int `json:"height"`
|
|
}{}
|
|
err = json.Unmarshal(input, &p4)
|
|
if assert.NoError(t, err) {
|
|
assert.EqualValues(t, 22, p4.Height)
|
|
assert.EqualValues(t, []byte{0x12, 0x34}, p4.Value)
|
|
}
|
|
|
|
// so, let's use this trick...
|
|
// dynamic keys on map, and we can deserialize to the desired types
|
|
var p5 map[string]*json.RawMessage
|
|
err = json.Unmarshal(input, &p5)
|
|
if assert.NoError(t, err) {
|
|
var h int
|
|
err = json.Unmarshal(*p5["height"], &h)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, 22, h)
|
|
}
|
|
|
|
var v bytes.HexBytes
|
|
err = json.Unmarshal(*p5["value"], &v)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, bytes.HexBytes{0x12, 0x34}, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseJSONArray(t *testing.T) {
|
|
input := []byte(`["1234",22]`)
|
|
|
|
// naive is float,string
|
|
var p1 []interface{}
|
|
err := json.Unmarshal(input, &p1)
|
|
if assert.NoError(t, err) {
|
|
v, ok := p1[0].(string)
|
|
if assert.True(t, ok, "%#v", p1[0]) {
|
|
assert.EqualValues(t, "1234", v)
|
|
}
|
|
h, ok := p1[1].(float64)
|
|
if assert.True(t, ok, "%#v", p1[1]) {
|
|
assert.EqualValues(t, 22, h)
|
|
}
|
|
}
|
|
|
|
// preloading map with values helps here (unlike map - p2 above)
|
|
tmp := 0
|
|
p2 := []interface{}{&bytes.HexBytes{}, &tmp}
|
|
err = json.Unmarshal(input, &p2)
|
|
if assert.NoError(t, err) {
|
|
v, ok := p2[0].(*bytes.HexBytes)
|
|
if assert.True(t, ok, "%#v", p2[0]) {
|
|
assert.EqualValues(t, []byte{0x12, 0x34}, *v)
|
|
}
|
|
h, ok := p2[1].(*int)
|
|
if assert.True(t, ok, "%#v", p2[1]) {
|
|
assert.EqualValues(t, 22, *h)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseJSONRPC(t *testing.T) {
|
|
demo := func(ctx context.Context, height int, name string) error { return nil }
|
|
call := NewRPCFunc(demo, "height", "name")
|
|
|
|
cases := []struct {
|
|
raw string
|
|
height int64
|
|
name string
|
|
fail bool
|
|
}{
|
|
// should parse
|
|
{`["7", "flew"]`, 7, "flew", false},
|
|
{`{"name": "john", "height": "22"}`, 22, "john", false},
|
|
// defaults
|
|
{`{"name": "solo", "unused": "stuff"}`, 0, "solo", false},
|
|
// should fail - wrong types/length
|
|
{`["flew", 7]`, 0, "", true},
|
|
{`[7,"flew",100]`, 0, "", true},
|
|
{`{"name": -12, "height": "fred"}`, 0, "", true},
|
|
}
|
|
for idx, tc := range cases {
|
|
i := strconv.Itoa(idx)
|
|
data := []byte(tc.raw)
|
|
vals, err := jsonParamsToArgs(call, data)
|
|
if tc.fail {
|
|
assert.Error(t, err, i)
|
|
} else {
|
|
assert.NoError(t, err, "%s: %+v", i, err)
|
|
if assert.Equal(t, 2, len(vals), i) {
|
|
assert.Equal(t, tc.height, vals[0].Int(), i)
|
|
assert.Equal(t, tc.name, vals[1].String(), i)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func TestParseURI(t *testing.T) {
|
|
demo := func(ctx context.Context, height int, name string) error { return nil }
|
|
call := NewRPCFunc(demo, "height", "name")
|
|
|
|
cases := []struct {
|
|
raw []string
|
|
height int64
|
|
name string
|
|
fail bool
|
|
}{
|
|
// can parse numbers unquoted and strings quoted
|
|
{[]string{"7", `"flew"`}, 7, "flew", false},
|
|
{[]string{"22", `"john"`}, 22, "john", false},
|
|
{[]string{"-10", `"bob"`}, -10, "bob", false},
|
|
// can parse numbers quoted, too
|
|
{[]string{`"7"`, `"flew"`}, 7, "flew", false},
|
|
{[]string{`"-10"`, `"bob"`}, -10, "bob", false},
|
|
// cant parse strings uquoted
|
|
{[]string{`"-10"`, `bob`}, -10, "bob", true},
|
|
}
|
|
for idx, tc := range cases {
|
|
i := strconv.Itoa(idx)
|
|
// data := []byte(tc.raw)
|
|
url := fmt.Sprintf(
|
|
"test.com/method?height=%v&name=%v",
|
|
tc.raw[0], tc.raw[1])
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
assert.NoError(t, err)
|
|
vals, err := httpParamsToArgs(call, req)
|
|
if tc.fail {
|
|
assert.Error(t, err, i)
|
|
} else {
|
|
assert.NoError(t, err, "%s: %+v", i, err)
|
|
if assert.Equal(t, 2, len(vals), i) {
|
|
assert.Equal(t, tc.height, vals[0].Int(), i)
|
|
assert.Equal(t, tc.name, vals[1].String(), i)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|