123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- // Copyright 2013 Julien Schmidt. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be found
- // in the LICENSE file.
- package httprouter
- import (
- "errors"
- "fmt"
- "net/http"
- "net/http/httptest"
- "reflect"
- "testing"
- )
- type mockResponseWriter struct{}
- func (m *mockResponseWriter) Header() (h http.Header) {
- return http.Header{}
- }
- func (m *mockResponseWriter) Write(p []byte) (n int, err error) {
- return len(p), nil
- }
- func (m *mockResponseWriter) WriteString(s string) (n int, err error) {
- return len(s), nil
- }
- func (m *mockResponseWriter) WriteHeader(int) {}
- func TestParams(t *testing.T) {
- ps := Params{
- Param{"param1", "value1"},
- Param{"param2", "value2"},
- Param{"param3", "value3"},
- }
- for i := range ps {
- if val := ps.ByName(ps[i].Key); val != ps[i].Value {
- t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value)
- }
- }
- if val := ps.ByName("noKey"); val != "" {
- t.Errorf("Expected empty string for not found key; got: %s", val)
- }
- }
- func TestRouter(t *testing.T) {
- router := New()
- routed := false
- router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) {
- routed = true
- want := Params{Param{"name", "gopher"}}
- if !reflect.DeepEqual(ps, want) {
- t.Fatalf("wrong wildcard values: want %v, got %v", want, ps)
- }
- })
- w := new(mockResponseWriter)
- req, _ := http.NewRequest("GET", "/user/gopher", nil)
- router.ServeHTTP(w, req)
- if !routed {
- t.Fatal("routing failed")
- }
- }
- type handlerStruct struct {
- handled *bool
- }
- func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- *h.handled = true
- }
- func TestRouterAPI(t *testing.T) {
- var get, head, options, post, put, patch, delete, handler, handlerFunc bool
- httpHandler := handlerStruct{&handler}
- router := New()
- router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
- get = true
- })
- router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
- head = true
- })
- router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
- options = true
- })
- router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
- post = true
- })
- router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
- put = true
- })
- router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
- patch = true
- })
- router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
- delete = true
- })
- router.Handler("GET", "/Handler", httpHandler)
- router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
- handlerFunc = true
- })
- w := new(mockResponseWriter)
- r, _ := http.NewRequest("GET", "/GET", nil)
- router.ServeHTTP(w, r)
- if !get {
- t.Error("routing GET failed")
- }
- r, _ = http.NewRequest("HEAD", "/GET", nil)
- router.ServeHTTP(w, r)
- if !head {
- t.Error("routing HEAD failed")
- }
- r, _ = http.NewRequest("OPTIONS", "/GET", nil)
- router.ServeHTTP(w, r)
- if !options {
- t.Error("routing OPTIONS failed")
- }
- r, _ = http.NewRequest("POST", "/POST", nil)
- router.ServeHTTP(w, r)
- if !post {
- t.Error("routing POST failed")
- }
- r, _ = http.NewRequest("PUT", "/PUT", nil)
- router.ServeHTTP(w, r)
- if !put {
- t.Error("routing PUT failed")
- }
- r, _ = http.NewRequest("PATCH", "/PATCH", nil)
- router.ServeHTTP(w, r)
- if !patch {
- t.Error("routing PATCH failed")
- }
- r, _ = http.NewRequest("DELETE", "/DELETE", nil)
- router.ServeHTTP(w, r)
- if !delete {
- t.Error("routing DELETE failed")
- }
- r, _ = http.NewRequest("GET", "/Handler", nil)
- router.ServeHTTP(w, r)
- if !handler {
- t.Error("routing Handler failed")
- }
- r, _ = http.NewRequest("GET", "/HandlerFunc", nil)
- router.ServeHTTP(w, r)
- if !handlerFunc {
- t.Error("routing HandlerFunc failed")
- }
- }
- func TestRouterRoot(t *testing.T) {
- router := New()
- recv := catchPanic(func() {
- router.GET("noSlashRoot", nil)
- })
- if recv == nil {
- t.Fatal("registering path not beginning with '/' did not panic")
- }
- }
- func TestRouterChaining(t *testing.T) {
- router1 := New()
- router2 := New()
- router1.NotFound = router2
- fooHit := false
- router1.POST("/foo", func(w http.ResponseWriter, req *http.Request, _ Params) {
- fooHit = true
- w.WriteHeader(http.StatusOK)
- })
- barHit := false
- router2.POST("/bar", func(w http.ResponseWriter, req *http.Request, _ Params) {
- barHit = true
- w.WriteHeader(http.StatusOK)
- })
- r, _ := http.NewRequest("POST", "/foo", nil)
- w := httptest.NewRecorder()
- router1.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK && fooHit) {
- t.Errorf("Regular routing failed with router chaining.")
- t.FailNow()
- }
- r, _ = http.NewRequest("POST", "/bar", nil)
- w = httptest.NewRecorder()
- router1.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK && barHit) {
- t.Errorf("Chained routing failed with router chaining.")
- t.FailNow()
- }
- r, _ = http.NewRequest("POST", "/qax", nil)
- w = httptest.NewRecorder()
- router1.ServeHTTP(w, r)
- if !(w.Code == http.StatusNotFound) {
- t.Errorf("NotFound behavior failed with router chaining.")
- t.FailNow()
- }
- }
- func TestRouterOPTIONS(t *testing.T) {
- handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
- router := New()
- router.POST("/path", handlerFunc)
- // test not allowed
- // * (server)
- r, _ := http.NewRequest("OPTIONS", "*", nil)
- w := httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK) {
- t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
- } else if allow := w.Header().Get("Allow"); allow != "POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- // path
- r, _ = http.NewRequest("OPTIONS", "/path", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK) {
- t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
- } else if allow := w.Header().Get("Allow"); allow != "POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- r, _ = http.NewRequest("OPTIONS", "/doesnotexist", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusNotFound) {
- t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
- }
- // add another method
- router.GET("/path", handlerFunc)
- // test again
- // * (server)
- r, _ = http.NewRequest("OPTIONS", "*", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK) {
- t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
- } else if allow := w.Header().Get("Allow"); allow != "POST, GET, OPTIONS" && allow != "GET, POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- // path
- r, _ = http.NewRequest("OPTIONS", "/path", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK) {
- t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
- } else if allow := w.Header().Get("Allow"); allow != "POST, GET, OPTIONS" && allow != "GET, POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- // custom handler
- var custom bool
- router.OPTIONS("/path", func(w http.ResponseWriter, r *http.Request, _ Params) {
- custom = true
- })
- // test again
- // * (server)
- r, _ = http.NewRequest("OPTIONS", "*", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK) {
- t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
- } else if allow := w.Header().Get("Allow"); allow != "POST, GET, OPTIONS" && allow != "GET, POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- if custom {
- t.Error("custom handler called on *")
- }
- // path
- r, _ = http.NewRequest("OPTIONS", "/path", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusOK) {
- t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
- }
- if !custom {
- t.Error("custom handler not called")
- }
- }
- func TestRouterNotAllowed(t *testing.T) {
- handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
- router := New()
- router.POST("/path", handlerFunc)
- // test not allowed
- r, _ := http.NewRequest("GET", "/path", nil)
- w := httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusMethodNotAllowed) {
- t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
- } else if allow := w.Header().Get("Allow"); allow != "POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- // add another method
- router.DELETE("/path", handlerFunc)
- router.OPTIONS("/path", handlerFunc) // must be ignored
- // test again
- r, _ = http.NewRequest("GET", "/path", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == http.StatusMethodNotAllowed) {
- t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
- } else if allow := w.Header().Get("Allow"); allow != "POST, DELETE, OPTIONS" && allow != "DELETE, POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- // test custom handler
- w = httptest.NewRecorder()
- responseText := "custom method"
- router.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- w.WriteHeader(http.StatusTeapot)
- w.Write([]byte(responseText))
- })
- router.ServeHTTP(w, r)
- if got := w.Body.String(); !(got == responseText) {
- t.Errorf("unexpected response got %q want %q", got, responseText)
- }
- if w.Code != http.StatusTeapot {
- t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
- }
- if allow := w.Header().Get("Allow"); allow != "POST, DELETE, OPTIONS" && allow != "DELETE, POST, OPTIONS" {
- t.Error("unexpected Allow header value: " + allow)
- }
- }
- func TestRouterNotFound(t *testing.T) {
- handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
- router := New()
- router.GET("/path", handlerFunc)
- router.GET("/dir/", handlerFunc)
- router.GET("/", handlerFunc)
- testRoutes := []struct {
- route string
- code int
- header string
- }{
- {"/path/", 301, "map[Location:[/path]]"}, // TSR -/
- {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/
- {"", 301, "map[Location:[/]]"}, // TSR +/
- {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case
- {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case
- {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/
- {"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/
- {"/../path", 301, "map[Location:[/path]]"}, // CleanPath
- {"/nope", 404, ""}, // NotFound
- }
- for _, tr := range testRoutes {
- r, _ := http.NewRequest("GET", tr.route, nil)
- w := httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) {
- t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header())
- }
- }
- // Test custom not found handler
- var notFound bool
- router.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- rw.WriteHeader(404)
- notFound = true
- })
- r, _ := http.NewRequest("GET", "/nope", nil)
- w := httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == 404 && notFound == true) {
- t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
- }
- // Test other method than GET (want 307 instead of 301)
- router.PATCH("/path", handlerFunc)
- r, _ = http.NewRequest("PATCH", "/path/", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
- t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
- }
- // Test special case where no node for the prefix "/" exists
- router = New()
- router.GET("/a", handlerFunc)
- r, _ = http.NewRequest("GET", "/", nil)
- w = httptest.NewRecorder()
- router.ServeHTTP(w, r)
- if !(w.Code == 404) {
- t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
- }
- }
- func TestRouterPanicHandler(t *testing.T) {
- router := New()
- panicHandled := false
- router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) {
- panicHandled = true
- }
- router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) {
- panic("oops!")
- })
- w := new(mockResponseWriter)
- req, _ := http.NewRequest("PUT", "/user/gopher", nil)
- defer func() {
- if rcv := recover(); rcv != nil {
- t.Fatal("handling panic failed")
- }
- }()
- router.ServeHTTP(w, req)
- if !panicHandled {
- t.Fatal("simulating failed")
- }
- }
- func TestRouterLookup(t *testing.T) {
- routed := false
- wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {
- routed = true
- }
- wantParams := Params{Param{"name", "gopher"}}
- router := New()
- // try empty router first
- handle, _, tsr := router.Lookup("GET", "/nope")
- if handle != nil {
- t.Fatalf("Got handle for unregistered pattern: %v", handle)
- }
- if tsr {
- t.Error("Got wrong TSR recommendation!")
- }
- // insert route and try again
- router.GET("/user/:name", wantHandle)
- handle, params, tsr := router.Lookup("GET", "/user/gopher")
- if handle == nil {
- t.Fatal("Got no handle!")
- } else {
- handle(nil, nil, nil)
- if !routed {
- t.Fatal("Routing failed!")
- }
- }
- if !reflect.DeepEqual(params, wantParams) {
- t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
- }
- handle, _, tsr = router.Lookup("GET", "/user/gopher/")
- if handle != nil {
- t.Fatalf("Got handle for unregistered pattern: %v", handle)
- }
- if !tsr {
- t.Error("Got no TSR recommendation!")
- }
- handle, _, tsr = router.Lookup("GET", "/nope")
- if handle != nil {
- t.Fatalf("Got handle for unregistered pattern: %v", handle)
- }
- if tsr {
- t.Error("Got wrong TSR recommendation!")
- }
- }
- type mockFileSystem struct {
- opened bool
- }
- func (mfs *mockFileSystem) Open(name string) (http.File, error) {
- mfs.opened = true
- return nil, errors.New("this is just a mock")
- }
- func TestRouterServeFiles(t *testing.T) {
- router := New()
- mfs := &mockFileSystem{}
- recv := catchPanic(func() {
- router.ServeFiles("/noFilepath", mfs)
- })
- if recv == nil {
- t.Fatal("registering path not ending with '*filepath' did not panic")
- }
- router.ServeFiles("/*filepath", mfs)
- w := new(mockResponseWriter)
- r, _ := http.NewRequest("GET", "/favicon.ico", nil)
- router.ServeHTTP(w, r)
- if !mfs.opened {
- t.Error("serving file failed")
- }
- }
|