github.com/lingyao2333/mo-zero@v1.4.1/rest/httpx/requests_test.go (about)

     1  package httpx
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/lingyao2333/mo-zero/rest/internal/header"
    11  	"github.com/lingyao2333/mo-zero/rest/pathvar"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  func TestParseForm(t *testing.T) {
    16  	var v struct {
    17  		Name    string  `form:"name"`
    18  		Age     int     `form:"age"`
    19  		Percent float64 `form:"percent,optional"`
    20  	}
    21  
    22  	r, err := http.NewRequest(http.MethodGet, "/a?name=hello&age=18&percent=3.4", http.NoBody)
    23  	assert.Nil(t, err)
    24  	assert.Nil(t, Parse(r, &v))
    25  	assert.Equal(t, "hello", v.Name)
    26  	assert.Equal(t, 18, v.Age)
    27  	assert.Equal(t, 3.4, v.Percent)
    28  }
    29  
    30  func TestParseForm_Error(t *testing.T) {
    31  	var v struct {
    32  		Name string `form:"name"`
    33  		Age  int    `form:"age"`
    34  	}
    35  
    36  	r := httptest.NewRequest(http.MethodGet, "/a?name=hello;", http.NoBody)
    37  	assert.NotNil(t, ParseForm(r, &v))
    38  }
    39  
    40  func TestParseHeader(t *testing.T) {
    41  	tests := []struct {
    42  		name   string
    43  		value  string
    44  		expect map[string]string
    45  	}{
    46  		{
    47  			name:   "empty",
    48  			value:  "",
    49  			expect: map[string]string{},
    50  		},
    51  		{
    52  			name:   "regular",
    53  			value:  "key=value",
    54  			expect: map[string]string{"key": "value"},
    55  		},
    56  		{
    57  			name:   "next empty",
    58  			value:  "key=value;",
    59  			expect: map[string]string{"key": "value"},
    60  		},
    61  		{
    62  			name:   "regular",
    63  			value:  "key=value;foo",
    64  			expect: map[string]string{"key": "value"},
    65  		},
    66  	}
    67  
    68  	for _, test := range tests {
    69  		test := test
    70  		t.Run(test.name, func(t *testing.T) {
    71  			t.Parallel()
    72  
    73  			m := ParseHeader(test.value)
    74  			assert.EqualValues(t, test.expect, m)
    75  		})
    76  	}
    77  }
    78  
    79  func TestParsePath(t *testing.T) {
    80  	var v struct {
    81  		Name string `path:"name"`
    82  		Age  int    `path:"age"`
    83  	}
    84  
    85  	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
    86  	r = pathvar.WithVars(r, map[string]string{
    87  		"name": "foo",
    88  		"age":  "18",
    89  	})
    90  	err := Parse(r, &v)
    91  	assert.Nil(t, err)
    92  	assert.Equal(t, "foo", v.Name)
    93  	assert.Equal(t, 18, v.Age)
    94  }
    95  
    96  func TestParsePath_Error(t *testing.T) {
    97  	var v struct {
    98  		Name string `path:"name"`
    99  		Age  int    `path:"age"`
   100  	}
   101  
   102  	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
   103  	r = pathvar.WithVars(r, map[string]string{
   104  		"name": "foo",
   105  	})
   106  	assert.NotNil(t, Parse(r, &v))
   107  }
   108  
   109  func TestParseFormOutOfRange(t *testing.T) {
   110  	var v struct {
   111  		Age int `form:"age,range=[10:20)"`
   112  	}
   113  
   114  	tests := []struct {
   115  		url  string
   116  		pass bool
   117  	}{
   118  		{
   119  			url:  "/a?age=5",
   120  			pass: false,
   121  		},
   122  		{
   123  			url:  "/a?age=10",
   124  			pass: true,
   125  		},
   126  		{
   127  			url:  "/a?age=15",
   128  			pass: true,
   129  		},
   130  		{
   131  			url:  "/a?age=20",
   132  			pass: false,
   133  		},
   134  		{
   135  			url:  "/a?age=28",
   136  			pass: false,
   137  		},
   138  	}
   139  
   140  	for _, test := range tests {
   141  		r, err := http.NewRequest(http.MethodGet, test.url, http.NoBody)
   142  		assert.Nil(t, err)
   143  
   144  		err = Parse(r, &v)
   145  		if test.pass {
   146  			assert.Nil(t, err)
   147  		} else {
   148  			assert.NotNil(t, err)
   149  		}
   150  	}
   151  }
   152  
   153  func TestParseMultipartForm(t *testing.T) {
   154  	var v struct {
   155  		Name string `form:"name"`
   156  		Age  int    `form:"age"`
   157  	}
   158  
   159  	body := strings.Replace(`----------------------------220477612388154780019383
   160  Content-Disposition: form-data; name="name"
   161  
   162  kevin
   163  ----------------------------220477612388154780019383
   164  Content-Disposition: form-data; name="age"
   165  
   166  18
   167  ----------------------------220477612388154780019383--`, "\n", "\r\n", -1)
   168  
   169  	r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
   170  	r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
   171  
   172  	assert.Nil(t, Parse(r, &v))
   173  	assert.Equal(t, "kevin", v.Name)
   174  	assert.Equal(t, 18, v.Age)
   175  }
   176  
   177  func TestParseMultipartFormWrongBoundary(t *testing.T) {
   178  	var v struct {
   179  		Name string `form:"name"`
   180  		Age  int    `form:"age"`
   181  	}
   182  
   183  	body := strings.Replace(`----------------------------22047761238815478001938
   184  Content-Disposition: form-data; name="name"
   185  
   186  kevin
   187  ----------------------------22047761238815478001938
   188  Content-Disposition: form-data; name="age"
   189  
   190  18
   191  ----------------------------22047761238815478001938--`, "\n", "\r\n", -1)
   192  
   193  	r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
   194  	r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
   195  
   196  	assert.NotNil(t, Parse(r, &v))
   197  }
   198  
   199  func TestParseJsonBody(t *testing.T) {
   200  	t.Run("has body", func(t *testing.T) {
   201  		var v struct {
   202  			Name string `json:"name"`
   203  			Age  int    `json:"age"`
   204  		}
   205  
   206  		body := `{"name":"kevin", "age": 18}`
   207  		r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
   208  		r.Header.Set(ContentType, header.JsonContentType)
   209  
   210  		assert.Nil(t, Parse(r, &v))
   211  		assert.Equal(t, "kevin", v.Name)
   212  		assert.Equal(t, 18, v.Age)
   213  	})
   214  
   215  	t.Run("hasn't body", func(t *testing.T) {
   216  		var v struct {
   217  			Name string `json:"name,optional"`
   218  			Age  int    `json:"age,optional"`
   219  		}
   220  
   221  		r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
   222  		assert.Nil(t, Parse(r, &v))
   223  		assert.Equal(t, "", v.Name)
   224  		assert.Equal(t, 0, v.Age)
   225  	})
   226  }
   227  
   228  func TestParseRequired(t *testing.T) {
   229  	v := struct {
   230  		Name    string  `form:"name"`
   231  		Percent float64 `form:"percent"`
   232  	}{}
   233  
   234  	r, err := http.NewRequest(http.MethodGet, "/a?name=hello", http.NoBody)
   235  	assert.Nil(t, err)
   236  	assert.NotNil(t, Parse(r, &v))
   237  }
   238  
   239  func TestParseOptions(t *testing.T) {
   240  	v := struct {
   241  		Position int8 `form:"pos,options=1|2"`
   242  	}{}
   243  
   244  	r, err := http.NewRequest(http.MethodGet, "/a?pos=4", http.NoBody)
   245  	assert.Nil(t, err)
   246  	assert.NotNil(t, Parse(r, &v))
   247  }
   248  
   249  func TestParseHeaders(t *testing.T) {
   250  	type AnonymousStruct struct {
   251  		XRealIP string `header:"x-real-ip"`
   252  		Accept  string `header:"Accept,optional"`
   253  	}
   254  	v := struct {
   255  		Name          string   `header:"name,optional"`
   256  		Percent       string   `header:"percent"`
   257  		Addrs         []string `header:"addrs"`
   258  		XForwardedFor string   `header:"X-Forwarded-For,optional"`
   259  		AnonymousStruct
   260  	}{}
   261  	request, err := http.NewRequest("POST", "/", http.NoBody)
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	request.Header.Set("name", "chenquan")
   266  	request.Header.Set("percent", "1")
   267  	request.Header.Add("addrs", "addr1")
   268  	request.Header.Add("addrs", "addr2")
   269  	request.Header.Add("X-Forwarded-For", "10.0.10.11")
   270  	request.Header.Add("x-real-ip", "10.0.11.10")
   271  	request.Header.Add("Accept", header.JsonContentType)
   272  	err = ParseHeaders(request, &v)
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	assert.Equal(t, "chenquan", v.Name)
   277  	assert.Equal(t, "1", v.Percent)
   278  	assert.Equal(t, []string{"addr1", "addr2"}, v.Addrs)
   279  	assert.Equal(t, "10.0.10.11", v.XForwardedFor)
   280  	assert.Equal(t, "10.0.11.10", v.XRealIP)
   281  	assert.Equal(t, header.JsonContentType, v.Accept)
   282  }
   283  
   284  func TestParseHeaders_Error(t *testing.T) {
   285  	v := struct {
   286  		Name string `header:"name"`
   287  		Age  int    `header:"age"`
   288  	}{}
   289  
   290  	r := httptest.NewRequest("POST", "/", http.NoBody)
   291  	r.Header.Set("name", "foo")
   292  	assert.NotNil(t, Parse(r, &v))
   293  }
   294  
   295  func BenchmarkParseRaw(b *testing.B) {
   296  	r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
   297  	if err != nil {
   298  		b.Fatal(err)
   299  	}
   300  
   301  	for i := 0; i < b.N; i++ {
   302  		v := struct {
   303  			Name    string  `form:"name"`
   304  			Age     int     `form:"age"`
   305  			Percent float64 `form:"percent,optional"`
   306  		}{}
   307  
   308  		v.Name = r.FormValue("name")
   309  		v.Age, err = strconv.Atoi(r.FormValue("age"))
   310  		if err != nil {
   311  			b.Fatal(err)
   312  		}
   313  		v.Percent, err = strconv.ParseFloat(r.FormValue("percent"), 64)
   314  		if err != nil {
   315  			b.Fatal(err)
   316  		}
   317  	}
   318  }
   319  
   320  func BenchmarkParseAuto(b *testing.B) {
   321  	r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
   322  	if err != nil {
   323  		b.Fatal(err)
   324  	}
   325  
   326  	for i := 0; i < b.N; i++ {
   327  		v := struct {
   328  			Name    string  `form:"name"`
   329  			Age     int     `form:"age"`
   330  			Percent float64 `form:"percent,optional"`
   331  		}{}
   332  
   333  		if err = Parse(r, &v); err != nil {
   334  			b.Fatal(err)
   335  		}
   336  	}
   337  }