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 }