|  | // 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 { | 
|  | handeled *bool | 
|  | } | 
|  |  | 
|  | func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { | 
|  | *h.handeled = 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 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()) | 
|  | } | 
|  |  | 
|  | w = httptest.NewRecorder() | 
|  | responseText := "custom method" | 
|  | router.MethodNotAllowed = 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) | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 = 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") | 
|  | } | 
|  | } |