github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/framework/http/httprouter/router_test.go (about) 1 // Copyright 2013 Julien Schmidt. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be found 3 // in the LICENSE file. 4 5 package httprouter 6 7 import ( 8 "net/http" 9 "net/http/httptest" 10 "testing" 11 ) 12 13 type mockResponseWriter struct{} 14 15 func (m *mockResponseWriter) Header() (h http.Header) { 16 return http.Header{} 17 } 18 19 func (m *mockResponseWriter) Write(p []byte) (n int, err error) { 20 return len(p), nil 21 } 22 23 func (m *mockResponseWriter) WriteString(s string) (n int, err error) { 24 return len(s), nil 25 } 26 27 func (m *mockResponseWriter) WriteHeader(int) {} 28 29 func TestParams(t *testing.T) { 30 ps := Params{ 31 Param{"param1", "value1"}, 32 Param{"param2", "value2"}, 33 Param{"param3", "value3"}, 34 } 35 for i := range ps { 36 if val := ps.ByName(ps[i].Key); val != ps[i].Value { 37 t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value) 38 } 39 } 40 if val := ps.ByName("noKey"); val != "" { 41 t.Errorf("Expected empty string for not found key; got: %s", val) 42 } 43 } 44 45 type handlerStruct struct { 46 handled *bool 47 } 48 49 func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { 50 *h.handled = true 51 } 52 53 func TestRouterAPI(t *testing.T) { 54 var get, head, options, post, put, patch, delete, handler, handlerFunc bool 55 56 httpHandler := handlerStruct{&handler} 57 58 router := New() 59 router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { 60 get = true 61 }) 62 router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { 63 head = true 64 }) 65 router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { 66 options = true 67 }) 68 router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) { 69 post = true 70 }) 71 router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) { 72 put = true 73 }) 74 router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) { 75 patch = true 76 }) 77 router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) { 78 delete = true 79 }) 80 router.Handler(http.MethodGet, "/Handler", httpHandler) 81 router.HandlerFunc(http.MethodGet, "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) { 82 handlerFunc = true 83 }) 84 85 w := new(mockResponseWriter) 86 87 r, _ := http.NewRequest(http.MethodGet, "/GET", nil) 88 router.ServeHTTP(w, r) 89 if !get { 90 t.Error("routing GET failed") 91 } 92 93 r, _ = http.NewRequest(http.MethodHead, "/GET", nil) 94 router.ServeHTTP(w, r) 95 if !head { 96 t.Error("routing HEAD failed") 97 } 98 99 r, _ = http.NewRequest(http.MethodOptions, "/GET", nil) 100 router.ServeHTTP(w, r) 101 if !options { 102 t.Error("routing OPTIONS failed") 103 } 104 105 r, _ = http.NewRequest(http.MethodPost, "/POST", nil) 106 router.ServeHTTP(w, r) 107 if !post { 108 t.Error("routing POST failed") 109 } 110 111 r, _ = http.NewRequest(http.MethodPut, "/PUT", nil) 112 router.ServeHTTP(w, r) 113 if !put { 114 t.Error("routing PUT failed") 115 } 116 117 r, _ = http.NewRequest(http.MethodPatch, "/PATCH", nil) 118 router.ServeHTTP(w, r) 119 if !patch { 120 t.Error("routing PATCH failed") 121 } 122 123 r, _ = http.NewRequest(http.MethodDelete, "/DELETE", nil) 124 router.ServeHTTP(w, r) 125 if !delete { 126 t.Error("routing DELETE failed") 127 } 128 129 r, _ = http.NewRequest(http.MethodGet, "/Handler", nil) 130 router.ServeHTTP(w, r) 131 if !handler { 132 t.Error("routing Handler failed") 133 } 134 135 r, _ = http.NewRequest(http.MethodGet, "/HandlerFunc", nil) 136 router.ServeHTTP(w, r) 137 if !handlerFunc { 138 t.Error("routing HandlerFunc failed") 139 } 140 } 141 142 func TestRouterChaining(t *testing.T) { 143 router1 := New() 144 router2 := New() 145 router1.NotFound = router2 146 147 fooHit := false 148 router1.POST("/foo", func(w http.ResponseWriter, req *http.Request, _ Params) { 149 fooHit = true 150 w.WriteHeader(http.StatusOK) 151 }) 152 153 barHit := false 154 router2.POST("/bar", func(w http.ResponseWriter, req *http.Request, _ Params) { 155 barHit = true 156 w.WriteHeader(http.StatusOK) 157 }) 158 159 r, _ := http.NewRequest(http.MethodPost, "/foo", nil) 160 w := httptest.NewRecorder() 161 router1.ServeHTTP(w, r) 162 if !(w.Code == http.StatusOK && fooHit) { 163 t.Errorf("Regular routing failed with router chaining.") 164 t.FailNow() 165 } 166 167 r, _ = http.NewRequest(http.MethodPost, "/bar", nil) 168 w = httptest.NewRecorder() 169 router1.ServeHTTP(w, r) 170 if !(w.Code == http.StatusOK && barHit) { 171 t.Errorf("Chained routing failed with router chaining.") 172 t.FailNow() 173 } 174 175 r, _ = http.NewRequest(http.MethodPost, "/qax", nil) 176 w = httptest.NewRecorder() 177 router1.ServeHTTP(w, r) 178 if !(w.Code == http.StatusNotFound) { 179 t.Errorf("NotFound behavior failed with router chaining.") 180 t.FailNow() 181 } 182 } 183 184 func BenchmarkAllowed(b *testing.B) { 185 handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} 186 187 router := New() 188 router.POST("/path", handlerFunc) 189 router.GET("/path", handlerFunc) 190 191 b.Run("Global", func(b *testing.B) { 192 b.ReportAllocs() 193 for i := 0; i < b.N; i++ { 194 _ = router.allowed("*", http.MethodOptions) 195 } 196 }) 197 b.Run("Path", func(b *testing.B) { 198 b.ReportAllocs() 199 for i := 0; i < b.N; i++ { 200 _ = router.allowed("/path", http.MethodOptions) 201 } 202 }) 203 } 204 205 func TestRouterOPTIONS(t *testing.T) { 206 handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} 207 208 router := New() 209 router.POST("/path", handlerFunc) 210 211 // test not allowed 212 // * (server) 213 r, _ := http.NewRequest(http.MethodOptions, "*", nil) 214 w := httptest.NewRecorder() 215 router.ServeHTTP(w, r) 216 if !(w.Code == http.StatusOK) { 217 t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header()) 218 } else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" { 219 t.Error("unexpected Allow header value: " + allow) 220 } 221 222 // path 223 r, _ = http.NewRequest(http.MethodOptions, "/path", nil) 224 w = httptest.NewRecorder() 225 router.ServeHTTP(w, r) 226 if !(w.Code == http.StatusOK) { 227 t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header()) 228 } else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" { 229 t.Error("unexpected Allow header value: " + allow) 230 } 231 232 r, _ = http.NewRequest(http.MethodOptions, "/doesnotexist", nil) 233 w = httptest.NewRecorder() 234 router.ServeHTTP(w, r) 235 if !(w.Code == http.StatusNotFound) { 236 t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header()) 237 } 238 239 // add another method 240 router.GET("/path", handlerFunc) 241 242 // set a global OPTIONS handler 243 router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 244 // Adjust status code to 204 245 w.WriteHeader(http.StatusNoContent) 246 }) 247 248 // test again 249 // * (server) 250 r, _ = http.NewRequest(http.MethodOptions, "*", nil) 251 w = httptest.NewRecorder() 252 router.ServeHTTP(w, r) 253 if !(w.Code == http.StatusNoContent) { 254 t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header()) 255 } else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" { 256 t.Error("unexpected Allow header value: " + allow) 257 } 258 259 // path 260 r, _ = http.NewRequest(http.MethodOptions, "/path", nil) 261 w = httptest.NewRecorder() 262 router.ServeHTTP(w, r) 263 if !(w.Code == http.StatusNoContent) { 264 t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header()) 265 } else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" { 266 t.Error("unexpected Allow header value: " + allow) 267 } 268 269 // custom handler 270 var custom bool 271 router.OPTIONS("/path", func(w http.ResponseWriter, r *http.Request, _ Params) { 272 custom = true 273 }) 274 275 // test again 276 // * (server) 277 r, _ = http.NewRequest(http.MethodOptions, "*", nil) 278 w = httptest.NewRecorder() 279 router.ServeHTTP(w, r) 280 if !(w.Code == http.StatusNoContent) { 281 t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header()) 282 } else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" { 283 t.Error("unexpected Allow header value: " + allow) 284 } 285 if custom { 286 t.Error("custom handler called on *") 287 } 288 289 // path 290 r, _ = http.NewRequest(http.MethodOptions, "/path", nil) 291 w = httptest.NewRecorder() 292 router.ServeHTTP(w, r) 293 if !(w.Code == http.StatusOK) { 294 t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header()) 295 } 296 if !custom { 297 t.Error("custom handler not called") 298 } 299 } 300 301 func TestRouterNotAllowed(t *testing.T) { 302 handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} 303 304 router := New() 305 router.POST("/path", handlerFunc) 306 307 // test not allowed 308 r, _ := http.NewRequest(http.MethodGet, "/path", nil) 309 w := httptest.NewRecorder() 310 router.ServeHTTP(w, r) 311 if !(w.Code == http.StatusMethodNotAllowed) { 312 t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header()) 313 } else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" { 314 t.Error("unexpected Allow header value: " + allow) 315 } 316 317 // add another method 318 router.DELETE("/path", handlerFunc) 319 router.OPTIONS("/path", handlerFunc) // must be ignored 320 321 // test again 322 r, _ = http.NewRequest(http.MethodGet, "/path", nil) 323 w = httptest.NewRecorder() 324 router.ServeHTTP(w, r) 325 if !(w.Code == http.StatusMethodNotAllowed) { 326 t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header()) 327 } else if allow := w.Header().Get("Allow"); allow != "DELETE, OPTIONS, POST" { 328 t.Error("unexpected Allow header value: " + allow) 329 } 330 331 // test custom handler 332 w = httptest.NewRecorder() 333 responseText := "custom method" 334 router.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 335 w.WriteHeader(http.StatusTeapot) 336 w.Write([]byte(responseText)) 337 }) 338 router.ServeHTTP(w, r) 339 if got := w.Body.String(); !(got == responseText) { 340 t.Errorf("unexpected response got %q want %q", got, responseText) 341 } 342 if w.Code != http.StatusTeapot { 343 t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot) 344 } 345 if allow := w.Header().Get("Allow"); allow != "DELETE, OPTIONS, POST" { 346 t.Error("unexpected Allow header value: " + allow) 347 } 348 }