mirror of https://github.com/gofiber/fiber.git
520 lines
12 KiB
Go
520 lines
12 KiB
Go
//nolint:wrapcheck,tagliatelle // We must not wrap errors in tests
|
|
package fiber
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
func Test_Binder(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
|
|
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
ctx.values = [maxParams]string{"id string"}
|
|
ctx.route = &Route{Params: []string{"id"}}
|
|
ctx.Request().SetBody([]byte(`{"name": "john doe"}`))
|
|
ctx.Request().Header.Set("content-type", "application/json")
|
|
|
|
var req struct {
|
|
ID *string `param:"id"`
|
|
}
|
|
|
|
var body struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
err := ctx.Bind().Req(&req).JSON(&body).Err()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "id string", *req.ID)
|
|
require.Equal(t, "john doe", body.Name)
|
|
}
|
|
|
|
func Test_Binder_Nested(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
c.Request().SetBody([]byte(``))
|
|
c.Request().Header.SetContentType("")
|
|
c.Request().URI().SetQueryString("name=tom&nested.and.age=10&nested.and.test=john")
|
|
|
|
// TODO: pointer support for structs
|
|
var req struct {
|
|
Name string `query:"name"`
|
|
Nested struct {
|
|
And struct {
|
|
Age *int `query:"age"`
|
|
Test string `query:"test"`
|
|
} `query:"and"`
|
|
} `query:"nested"`
|
|
}
|
|
|
|
err := c.Bind().Req(&req).Err()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "tom", req.Name)
|
|
require.Equal(t, "john", req.Nested.And.Test)
|
|
require.Equal(t, 10, *req.Nested.And.Age)
|
|
}
|
|
|
|
func Test_Binder_Nested_Slice(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
c.Request().SetBody([]byte(``))
|
|
c.Request().Header.SetContentType("")
|
|
c.Request().URI().SetQueryString("name=tom&data[0][name]=john&data[0][age]=10&data[1][name]=doe&data[1][age]=12")
|
|
|
|
var req struct {
|
|
Name string `query:"name"`
|
|
Data []struct {
|
|
Name string `query:"name"`
|
|
Age int `query:"age"`
|
|
} `query:"data"`
|
|
}
|
|
|
|
err := c.Bind().Req(&req).Err()
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(req.Data))
|
|
require.Equal(t, "john", req.Data[0].Name)
|
|
require.Equal(t, 10, req.Data[0].Age)
|
|
require.Equal(t, "doe", req.Data[1].Name)
|
|
require.Equal(t, 12, req.Data[1].Age)
|
|
require.Equal(t, "tom", req.Name)
|
|
}
|
|
|
|
/*func Test_Binder_Nested_Deeper_Slice(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
c.Request().SetBody([]byte(``))
|
|
c.Request().Header.SetContentType("")
|
|
c.Request().URI().SetQueryString("data[0][users][0][name]=john&data[0][users][0][age]=10&data[1][users][0][name]=doe&data[1][users][0][age]=12")
|
|
|
|
var req struct {
|
|
Data []struct {
|
|
Users []struct {
|
|
Name string `query:"name"`
|
|
Age int `query:"age"`
|
|
} `query:"subData"`
|
|
} `query:"data"`
|
|
}
|
|
|
|
err := c.Bind().Req(&req).Err()
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(req.Data))
|
|
require.Equal(t, "john", req.Data[0].Users[0].Name)
|
|
require.Equal(t, 10, req.Data[0].Users[0].Age)
|
|
require.Equal(t, "doe", req.Data[1].Users[0].Name)
|
|
require.Equal(t, 12, req.Data[1].Users[0].Age)
|
|
}*/
|
|
|
|
// go test -run Test_Bind_BasicType -v
|
|
func Test_Bind_BasicType(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
|
|
type Query struct {
|
|
Flag bool `query:"enable"`
|
|
|
|
I8 int8 `query:"i8"`
|
|
I16 int16 `query:"i16"`
|
|
I32 int32 `query:"i32"`
|
|
I64 int64 `query:"i64"`
|
|
I int `query:"i"`
|
|
|
|
U8 uint8 `query:"u8"`
|
|
U16 uint16 `query:"u16"`
|
|
U32 uint32 `query:"u32"`
|
|
U64 uint64 `query:"u64"`
|
|
U uint `query:"u"`
|
|
|
|
S string `query:"s"`
|
|
}
|
|
|
|
var q Query
|
|
|
|
const qs = "i8=88&i16=166&i32=322&i64=644&i=101&u8=77&u16=165&u32=321&u64=643&u=99&s=john&enable=true"
|
|
c.Request().URI().SetQueryString(qs)
|
|
require.NoError(t, c.Bind().Req(&q).Err())
|
|
|
|
require.Equal(t, Query{
|
|
Flag: true,
|
|
I8: 88,
|
|
I16: 166,
|
|
I32: 322,
|
|
I64: 644,
|
|
I: 101,
|
|
U8: 77,
|
|
U16: 165,
|
|
U32: 321,
|
|
U64: 643,
|
|
U: 99,
|
|
S: "john",
|
|
}, q)
|
|
|
|
type Query2 struct {
|
|
Flag []bool `query:"enable"`
|
|
|
|
I8 []int8 `query:"i8"`
|
|
I16 []int16 `query:"i16"`
|
|
I32 []int32 `query:"i32"`
|
|
I64 []int64 `query:"i64"`
|
|
I []int `query:"i"`
|
|
|
|
U8 []uint8 `query:"u8"`
|
|
U16 []uint16 `query:"u16"`
|
|
U32 []uint32 `query:"u32"`
|
|
U64 []uint64 `query:"u64"`
|
|
U []uint `query:"u"`
|
|
|
|
S []string `query:"s"`
|
|
}
|
|
|
|
var q2 Query2
|
|
|
|
c.Request().URI().SetQueryString(qs)
|
|
require.NoError(t, c.Bind().Req(&q2).Err())
|
|
|
|
require.Equal(t, Query2{
|
|
Flag: []bool{true},
|
|
I8: []int8{88},
|
|
I16: []int16{166},
|
|
I32: []int32{322},
|
|
I64: []int64{644},
|
|
I: []int{101},
|
|
U8: []uint8{77},
|
|
U16: []uint16{165},
|
|
U32: []uint32{321},
|
|
U64: []uint64{643},
|
|
U: []uint{99},
|
|
S: []string{"john"},
|
|
}, q2)
|
|
|
|
}
|
|
|
|
// go test -run Test_Bind_Query -v
|
|
func Test_Bind_Query(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
|
|
type Query struct {
|
|
ID int `query:"id"`
|
|
Name string `query:"name"`
|
|
Hobby []string `query:"hobby"`
|
|
}
|
|
|
|
var q Query
|
|
|
|
c.Request().SetBody([]byte{})
|
|
c.Request().Header.SetContentType("")
|
|
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football")
|
|
|
|
require.NoError(t, c.Bind().Req(&q).Err())
|
|
require.Equal(t, 2, len(q.Hobby))
|
|
|
|
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football")
|
|
require.NoError(t, c.Bind().Req(&q).Err())
|
|
require.Equal(t, 1, len(q.Hobby))
|
|
|
|
c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football")
|
|
require.NoError(t, c.Bind().Req(&q).Err())
|
|
require.Equal(t, 2, len(q.Hobby))
|
|
|
|
c.Request().URI().SetQueryString("")
|
|
require.NoError(t, c.Bind().Req(&q).Err())
|
|
require.Equal(t, 0, len(q.Hobby))
|
|
|
|
type Query2 struct {
|
|
Bool bool `query:"bool"`
|
|
ID int `query:"id"`
|
|
Name string `query:"name"`
|
|
Hobby string `query:"hobby"`
|
|
FavouriteDrinks string `query:"favouriteDrinks"`
|
|
Empty []string `query:"empty"`
|
|
Alloc []string `query:"alloc"`
|
|
No []int64 `query:"no"`
|
|
}
|
|
|
|
var q2 Query2
|
|
|
|
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1")
|
|
require.NoError(t, c.Bind().Req(&q2).Err())
|
|
require.Equal(t, "basketball,football", q2.Hobby)
|
|
require.Equal(t, "tom", q2.Name) // check value get overwritten
|
|
require.Equal(t, "milo,coke,pepsi", q2.FavouriteDrinks)
|
|
require.Equal(t, []string{}, q2.Empty)
|
|
require.Equal(t, []string{""}, q2.Alloc)
|
|
require.Equal(t, []int64{1}, q2.No)
|
|
|
|
type ArrayQuery struct {
|
|
Data []string `query:"data[]"`
|
|
}
|
|
var aq ArrayQuery
|
|
c.Request().URI().SetQueryString("data[]=john&data[]=doe")
|
|
require.NoError(t, c.Bind().Req(&aq).Err())
|
|
require.Equal(t, ArrayQuery{Data: []string{"john", "doe"}}, aq)
|
|
}
|
|
|
|
// go test -run Test_Bind_Resp_Header -v
|
|
func Test_Bind_Resp_Header(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
|
|
type resHeader struct {
|
|
Key string `respHeader:"k"`
|
|
|
|
Keys []string `respHeader:"keys"`
|
|
}
|
|
|
|
c.Set("k", "vv")
|
|
c.Response().Header.Add("keys", "v1")
|
|
c.Response().Header.Add("keys", "v2")
|
|
|
|
var q resHeader
|
|
require.NoError(t, c.Bind().Req(&q).Err())
|
|
require.Equal(t, "vv", q.Key)
|
|
require.Equal(t, []string{"v1", "v2"}, q.Keys)
|
|
}
|
|
|
|
var _ Binder = (*userCtxUnmarshaler)(nil)
|
|
|
|
type userCtxUnmarshaler struct {
|
|
V int
|
|
}
|
|
|
|
func (u *userCtxUnmarshaler) UnmarshalFiberCtx(ctx Ctx) error {
|
|
u.V++
|
|
return nil
|
|
}
|
|
|
|
// go test -run Test_Bind_CustomizedUnmarshaler -v
|
|
func Test_Bind_CustomizedUnmarshaler(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
|
|
type Req struct {
|
|
Key userCtxUnmarshaler
|
|
}
|
|
|
|
var r Req
|
|
require.NoError(t, c.Bind().Req(&r).Err())
|
|
require.Equal(t, 1, r.Key.V)
|
|
|
|
require.NoError(t, c.Bind().Req(&r).Err())
|
|
require.Equal(t, 1, r.Key.V)
|
|
}
|
|
|
|
// go test -run Test_Bind_TextUnmarshaler -v
|
|
func Test_Bind_TextUnmarshaler(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
|
|
type Req struct {
|
|
Time time.Time `query:"time"`
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
c.Request().URI().SetQueryString(url.Values{
|
|
"time": []string{now.Format(time.RFC3339Nano)},
|
|
}.Encode())
|
|
|
|
var q Req
|
|
require.NoError(t, c.Bind().Req(&q).Err())
|
|
require.Equal(t, false, q.Time.IsZero(), "time should not be zero")
|
|
require.Equal(t, true, q.Time.Before(now.Add(time.Second)))
|
|
require.Equal(t, true, q.Time.After(now.Add(-time.Second)))
|
|
}
|
|
|
|
// go test -run Test_Bind_error_message -v
|
|
func Test_Bind_error_message(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
|
|
type Req struct {
|
|
Time time.Time `query:"time"`
|
|
}
|
|
|
|
c.Request().URI().SetQueryString("time=john")
|
|
|
|
err := c.Bind().Req(&Req{}).Err()
|
|
|
|
require.Error(t, err)
|
|
require.Regexp(t, regexp.MustCompile(`unable to decode 'john' as time`), err.Error())
|
|
}
|
|
|
|
func Test_Bind_Form(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
|
|
c.Context().Request.Header.Set(HeaderContentType, MIMEApplicationForm)
|
|
c.Context().Request.SetBody([]byte(url.Values{
|
|
"username": {"u"},
|
|
"password": {"p"},
|
|
"likes": {"apple", "banana"},
|
|
}.Encode()))
|
|
|
|
type Req struct {
|
|
Username string `form:"username"`
|
|
Password string `form:"password"`
|
|
Likes []string `form:"likes"`
|
|
}
|
|
|
|
var r Req
|
|
err := c.Bind().Form(&r).Err()
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "u", r.Username)
|
|
require.Equal(t, "p", r.Password)
|
|
require.Equal(t, []string{"apple", "banana"}, r.Likes)
|
|
}
|
|
|
|
func Test_Bind_Multipart(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
boundary := multipart.NewWriter(nil).Boundary()
|
|
err := fasthttp.WriteMultipartForm(buf, &multipart.Form{
|
|
Value: map[string][]string{
|
|
"username": {"u"},
|
|
"password": {"p"},
|
|
"likes": {"apple", "banana"},
|
|
},
|
|
}, boundary)
|
|
|
|
require.NoError(t, err)
|
|
|
|
c.Context().Request.Header.Set(HeaderContentType, fmt.Sprintf("%s; boundary=%s", MIMEMultipartForm, boundary))
|
|
c.Context().Request.SetBody(buf.Bytes())
|
|
|
|
type Req struct {
|
|
Username string `multipart:"username"`
|
|
Password string `multipart:"password"`
|
|
Likes []string `multipart:"likes"`
|
|
}
|
|
|
|
var r Req
|
|
err = c.Bind().Multipart(&r).Err()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "u", r.Username)
|
|
require.Equal(t, "p", r.Password)
|
|
require.Equal(t, []string{"apple", "banana"}, r.Likes)
|
|
}
|
|
|
|
type Req struct {
|
|
ID string `params:"id"`
|
|
|
|
I int `query:"I"`
|
|
J int `query:"j"`
|
|
K int `query:"k"`
|
|
|
|
Token string `header:"x-auth"`
|
|
}
|
|
|
|
func getBenchCtx() Ctx {
|
|
app := New()
|
|
|
|
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
ctx.values = [maxParams]string{"id string"}
|
|
ctx.route = &Route{Params: []string{"id"}}
|
|
|
|
var u = fasthttp.URI{}
|
|
u.SetQueryString("j=1&j=123&k=-1")
|
|
ctx.Request().SetURI(&u)
|
|
|
|
ctx.Request().Header.Set("a-auth", "bearer tt")
|
|
|
|
return ctx
|
|
}
|
|
|
|
func Benchmark_Bind_by_hand(b *testing.B) {
|
|
ctx := getBenchCtx()
|
|
for i := 0; i < b.N; i++ {
|
|
var req Req
|
|
var err error
|
|
|
|
if raw := ctx.Params("id"); raw != "" {
|
|
req.ID = raw
|
|
}
|
|
|
|
if raw := ctx.Query("i"); raw != "" {
|
|
req.I, err = strconv.Atoi(raw)
|
|
if err != nil {
|
|
b.Error(err)
|
|
b.FailNow()
|
|
}
|
|
}
|
|
|
|
if raw := ctx.Query("j"); raw != "" {
|
|
req.J, err = strconv.Atoi(raw)
|
|
if err != nil {
|
|
b.Error(err)
|
|
b.FailNow()
|
|
}
|
|
}
|
|
|
|
if raw := ctx.Query("k"); raw != "" {
|
|
req.K, err = strconv.Atoi(raw)
|
|
if err != nil {
|
|
b.Error(err)
|
|
b.FailNow()
|
|
}
|
|
}
|
|
|
|
req.Token = ctx.Get("x-auth")
|
|
}
|
|
}
|
|
|
|
func Benchmark_Bind(b *testing.B) {
|
|
ctx := getBenchCtx()
|
|
for i := 0; i < b.N; i++ {
|
|
var v = Req{}
|
|
err := ctx.Bind().Req(&v).Err()
|
|
if err != nil {
|
|
b.Error(err)
|
|
b.FailNow()
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_Binder_Float(t *testing.T) {
|
|
t.Parallel()
|
|
app := New()
|
|
|
|
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
|
ctx.values = [maxParams]string{"3.14"}
|
|
ctx.route = &Route{Params: []string{"id"}}
|
|
|
|
var req struct {
|
|
ID1 float32 `param:"id"`
|
|
ID2 float64 `param:"id"`
|
|
}
|
|
|
|
err := ctx.Bind().Req(&req).Err()
|
|
require.NoError(t, err)
|
|
require.Equal(t, float32(3.14), req.ID1)
|
|
require.Equal(t, float64(3.14), req.ID2)
|
|
}
|