github.com/segakazzz/buffalo@v0.16.22-0.20210119082501-1f52048d3feb/router_test.go (about)

     1  package buffalo
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"path"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/gobuffalo/buffalo/render"
    12  	"github.com/gobuffalo/envy"
    13  	"github.com/gobuffalo/httptest"
    14  	"github.com/gobuffalo/packd"
    15  	"github.com/gobuffalo/packr/v2"
    16  	"github.com/gorilla/mux"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func testApp() *App {
    21  	a := New(Options{})
    22  	a.Redirect(http.StatusMovedPermanently, "/foo", "/bar")
    23  	a.GET("/bar", func(c Context) error {
    24  		return c.Render(http.StatusOK, render.String("bar"))
    25  	})
    26  
    27  	rt := a.Group("/router/tests")
    28  
    29  	h := func(c Context) error {
    30  		x := c.Request().Method + "|"
    31  		x += strings.TrimSuffix(c.Value("current_path").(string), "/")
    32  		return c.Render(http.StatusOK, render.String(x))
    33  	}
    34  
    35  	rt.GET("/", h)
    36  	rt.POST("/", h)
    37  	rt.PUT("/", h)
    38  	rt.DELETE("/", h)
    39  	rt.OPTIONS("/", h)
    40  	rt.PATCH("/", h)
    41  
    42  	a.ErrorHandlers[http.StatusMethodNotAllowed] = func(status int, err error, c Context) error {
    43  		res := c.Response()
    44  		res.WriteHeader(status)
    45  		res.Write([]byte("my custom 405"))
    46  		return nil
    47  	}
    48  	return a
    49  }
    50  
    51  func otherTestApp() *App {
    52  	a := New(Options{})
    53  	f := func(c Context) error {
    54  		req := c.Request()
    55  		return c.Render(http.StatusOK, render.String(req.Method+" - "+req.URL.String()))
    56  	}
    57  	a.GET("/foo", f)
    58  	a.POST("/bar", f)
    59  	a.DELETE("/baz/baz", f)
    60  	return a
    61  }
    62  
    63  func Test_MethodNotFoundError(t *testing.T) {
    64  	r := require.New(t)
    65  
    66  	a := New(Options{})
    67  	a.GET("/bar", func(c Context) error {
    68  		return c.Render(http.StatusOK, render.String("bar"))
    69  	})
    70  	a.ErrorHandlers[http.StatusMethodNotAllowed] = func(status int, err error, c Context) error {
    71  		res := c.Response()
    72  		res.WriteHeader(status)
    73  		res.Write([]byte("my custom 405"))
    74  		return nil
    75  	}
    76  	w := httptest.New(a)
    77  	res := w.HTML("/bar").Post(nil)
    78  	r.Equal(http.StatusMethodNotAllowed, res.Code)
    79  	r.Contains(res.Body.String(), "my custom 405")
    80  }
    81  
    82  func Test_Mount_Buffalo(t *testing.T) {
    83  	r := require.New(t)
    84  	a := testApp()
    85  	a.Mount("/admin", otherTestApp())
    86  
    87  	table := map[string]string{
    88  		"/foo":     "GET",
    89  		"/bar":     "POST",
    90  		"/baz/baz": "DELETE",
    91  	}
    92  	ts := httptest.NewServer(a)
    93  	defer ts.Close()
    94  
    95  	for u, m := range table {
    96  		p := fmt.Sprintf("%s/%s", ts.URL, path.Join("admin", u))
    97  		req, err := http.NewRequest(m, p, nil)
    98  		r.NoError(err)
    99  		res, err := http.DefaultClient.Do(req)
   100  		r.NoError(err)
   101  		b, _ := ioutil.ReadAll(res.Body)
   102  		r.Equal(fmt.Sprintf("%s - %s/", m, u), string(b))
   103  	}
   104  }
   105  
   106  func Test_Mount_Buffalo_on_Group(t *testing.T) {
   107  	r := require.New(t)
   108  	a := testApp()
   109  	g := a.Group("/users")
   110  	g.Mount("/admin", otherTestApp())
   111  
   112  	table := map[string]string{
   113  		"/foo":     "GET",
   114  		"/bar":     "POST",
   115  		"/baz/baz": "DELETE",
   116  	}
   117  	ts := httptest.NewServer(a)
   118  	defer ts.Close()
   119  
   120  	for u, m := range table {
   121  		p := fmt.Sprintf("%s/%s", ts.URL, path.Join("users", "admin", u))
   122  		req, err := http.NewRequest(m, p, nil)
   123  		r.NoError(err)
   124  		res, err := http.DefaultClient.Do(req)
   125  		r.NoError(err)
   126  		b, _ := ioutil.ReadAll(res.Body)
   127  		r.Equal(fmt.Sprintf("%s - %s/", m, u), string(b))
   128  	}
   129  }
   130  
   131  func muxer() http.Handler {
   132  	f := func(res http.ResponseWriter, req *http.Request) {
   133  		fmt.Fprintf(res, "%s - %s", req.Method, req.URL.String())
   134  	}
   135  	mux := mux.NewRouter()
   136  	mux.HandleFunc("/foo/", f).Methods("GET")
   137  	mux.HandleFunc("/bar/", f).Methods("POST")
   138  	mux.HandleFunc("/baz/baz/", f).Methods("DELETE")
   139  	return mux
   140  }
   141  
   142  func Test_Mount_Handler(t *testing.T) {
   143  	r := require.New(t)
   144  	a := testApp()
   145  	a.Mount("/admin", muxer())
   146  
   147  	table := map[string]string{
   148  		"/foo":     "GET",
   149  		"/bar":     "POST",
   150  		"/baz/baz": "DELETE",
   151  	}
   152  	ts := httptest.NewServer(a)
   153  	defer ts.Close()
   154  
   155  	for u, m := range table {
   156  		p := fmt.Sprintf("%s/%s", ts.URL, path.Join("admin", u))
   157  		req, err := http.NewRequest(m, p, nil)
   158  		r.NoError(err)
   159  		res, err := http.DefaultClient.Do(req)
   160  		r.NoError(err)
   161  		b, _ := ioutil.ReadAll(res.Body)
   162  		r.Equal(fmt.Sprintf("%s - %s/", m, u), string(b))
   163  	}
   164  }
   165  
   166  func Test_PreHandlers(t *testing.T) {
   167  	r := require.New(t)
   168  	a := testApp()
   169  	bh := func(c Context) error {
   170  		req := c.Request()
   171  		return c.Render(http.StatusOK, render.String(req.Method+"-"+req.URL.String()))
   172  	}
   173  	a.GET("/ph", bh)
   174  	a.POST("/ph", bh)
   175  	mh := func(res http.ResponseWriter, req *http.Request) {
   176  		if req.Method == "GET" {
   177  			res.WriteHeader(http.StatusTeapot)
   178  			res.Write([]byte("boo"))
   179  		}
   180  	}
   181  	a.PreHandlers = append(a.PreHandlers, http.HandlerFunc(mh))
   182  
   183  	ts := httptest.NewServer(a)
   184  	defer ts.Close()
   185  
   186  	table := []struct {
   187  		Code   int
   188  		Method string
   189  		Result string
   190  	}{
   191  		{Code: http.StatusTeapot, Method: "GET", Result: "boo"},
   192  		{Code: http.StatusOK, Method: "POST", Result: "POST-/ph/"},
   193  	}
   194  
   195  	for _, v := range table {
   196  		req, err := http.NewRequest(v.Method, ts.URL+"/ph", nil)
   197  		r.NoError(err)
   198  		res, err := http.DefaultClient.Do(req)
   199  		r.NoError(err)
   200  		b, err := ioutil.ReadAll(res.Body)
   201  		r.NoError(err)
   202  		r.Equal(v.Code, res.StatusCode)
   203  		r.Equal(v.Result, string(b))
   204  	}
   205  }
   206  
   207  func Test_PreWares(t *testing.T) {
   208  	r := require.New(t)
   209  	a := testApp()
   210  	bh := func(c Context) error {
   211  		req := c.Request()
   212  		return c.Render(http.StatusOK, render.String(req.Method+"-"+req.URL.String()))
   213  	}
   214  	a.GET("/ph", bh)
   215  	a.POST("/ph", bh)
   216  
   217  	mh := func(h http.Handler) http.Handler {
   218  		return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
   219  			if req.Method == "GET" {
   220  				res.WriteHeader(http.StatusTeapot)
   221  				res.Write([]byte("boo"))
   222  			}
   223  		})
   224  	}
   225  
   226  	a.PreWares = append(a.PreWares, mh)
   227  
   228  	ts := httptest.NewServer(a)
   229  	defer ts.Close()
   230  
   231  	table := []struct {
   232  		Code   int
   233  		Method string
   234  		Result string
   235  	}{
   236  		{Code: http.StatusTeapot, Method: "GET", Result: "boo"},
   237  		{Code: http.StatusOK, Method: "POST", Result: "POST-/ph/"},
   238  	}
   239  
   240  	for _, v := range table {
   241  		req, err := http.NewRequest(v.Method, ts.URL+"/ph", nil)
   242  		r.NoError(err)
   243  		res, err := http.DefaultClient.Do(req)
   244  		r.NoError(err)
   245  		b, err := ioutil.ReadAll(res.Body)
   246  		r.NoError(err)
   247  		r.Equal(v.Code, res.StatusCode)
   248  		r.Equal(v.Result, string(b))
   249  	}
   250  }
   251  
   252  func Test_Router(t *testing.T) {
   253  	r := require.New(t)
   254  
   255  	table := []string{
   256  		"GET",
   257  		"POST",
   258  		"PUT",
   259  		"DELETE",
   260  		"OPTIONS",
   261  		"PATCH",
   262  	}
   263  
   264  	ts := httptest.NewServer(testApp())
   265  	defer ts.Close()
   266  
   267  	for _, v := range table {
   268  		req, err := http.NewRequest(v, fmt.Sprintf("%s/router/tests", ts.URL), nil)
   269  		r.NoError(err)
   270  		res, err := http.DefaultClient.Do(req)
   271  		r.NoError(err)
   272  		b, _ := ioutil.ReadAll(res.Body)
   273  		r.Equal(fmt.Sprintf("%s|/router/tests", v), string(b))
   274  	}
   275  }
   276  
   277  func Test_Router_Group(t *testing.T) {
   278  	r := require.New(t)
   279  
   280  	a := testApp()
   281  	g := a.Group("/api/v1")
   282  	g.GET("/users", func(c Context) error {
   283  		return c.Render(http.StatusCreated, nil)
   284  	})
   285  
   286  	w := httptest.New(a)
   287  	res := w.HTML("/api/v1/users").Get()
   288  	r.Equal(http.StatusCreated, res.Code)
   289  }
   290  
   291  func Test_Router_Group_on_Group(t *testing.T) {
   292  	r := require.New(t)
   293  
   294  	a := testApp()
   295  	g := a.Group("/api/v1")
   296  	g.GET("/users", func(c Context) error {
   297  		return c.Render(http.StatusCreated, nil)
   298  	})
   299  	f := g.Group("/foo")
   300  	f.GET("/bar", func(c Context) error {
   301  		return c.Render(http.StatusTeapot, nil)
   302  	})
   303  
   304  	w := httptest.New(a)
   305  	res := w.HTML("/api/v1/foo/bar").Get()
   306  	r.Equal(http.StatusTeapot, res.Code)
   307  }
   308  
   309  func Test_Router_Group_Middleware(t *testing.T) {
   310  	r := require.New(t)
   311  
   312  	a := testApp()
   313  	a.Use(func(h Handler) Handler { return h })
   314  	r.Len(a.Middleware.stack, 5)
   315  
   316  	g := a.Group("/api/v1")
   317  	r.Len(a.Middleware.stack, 5)
   318  	r.Len(g.Middleware.stack, 5)
   319  
   320  	g.Use(func(h Handler) Handler { return h })
   321  	r.Len(a.Middleware.stack, 5)
   322  	r.Len(g.Middleware.stack, 6)
   323  }
   324  
   325  func Test_Router_Redirect(t *testing.T) {
   326  	r := require.New(t)
   327  	w := httptest.New(testApp())
   328  	res := w.HTML("/foo").Get()
   329  	r.Equal(http.StatusMovedPermanently, res.Code)
   330  	r.Equal("/bar", res.Location())
   331  }
   332  
   333  func Test_Router_ServeFiles(t *testing.T) {
   334  	r := require.New(t)
   335  
   336  	box := packd.NewMemoryBox()
   337  	box.AddString("foo.png", "foo")
   338  	a := New(Options{})
   339  	a.ServeFiles("/assets", box)
   340  
   341  	w := httptest.New(a)
   342  	res := w.HTML("/assets/foo.png").Get()
   343  
   344  	r.Equal(http.StatusOK, res.Code)
   345  	r.Equal("foo", res.Body.String())
   346  
   347  	r.NotEqual(res.Header().Get("ETag"), "")
   348  	r.Equal(res.Header().Get("Cache-Control"), "max-age=31536000")
   349  
   350  	envy.Set(AssetsAgeVarName, "3600")
   351  	w = httptest.New(a)
   352  	res = w.HTML("/assets/foo.png").Get()
   353  
   354  	r.Equal(http.StatusOK, res.Code)
   355  	r.Equal("foo", res.Body.String())
   356  
   357  	r.NotEqual(res.Header().Get("ETag"), "")
   358  	r.Equal(res.Header().Get("Cache-Control"), "max-age=3600")
   359  }
   360  
   361  func Test_Router_InvalidURL(t *testing.T) {
   362  	r := require.New(t)
   363  
   364  	box := packd.NewMemoryBox()
   365  	box.AddString("foo.png", "foo")
   366  	a := New(Options{})
   367  	a.ServeFiles("/", box)
   368  
   369  	w := httptest.New(a)
   370  	s := "/%25%7dn2zq0%3cscript%3ealert(1)%3c\\/script%3evea7f"
   371  
   372  	request, _ := http.NewRequest("GET", s, nil)
   373  	response := httptest.NewRecorder()
   374  
   375  	w.ServeHTTP(response, request)
   376  	r.Equal(http.StatusBadRequest, response.Code, "(400) BadRequest response is expected")
   377  }
   378  
   379  type WebResource struct {
   380  	BaseResource
   381  }
   382  
   383  // Edit default implementation. Returns a 404
   384  func (v WebResource) Edit(c Context) error {
   385  	return c.Error(http.StatusNotFound, fmt.Errorf("resource not implemented"))
   386  }
   387  
   388  // New default implementation. Returns a 404
   389  func (v WebResource) New(c Context) error {
   390  	return c.Error(http.StatusNotFound, fmt.Errorf("resource not implemented"))
   391  }
   392  
   393  func Test_App_NamedRoutes(t *testing.T) {
   394  
   395  	type CarsResource struct {
   396  		WebResource
   397  	}
   398  
   399  	type ResourcesResource struct {
   400  		WebResource
   401  	}
   402  
   403  	r := require.New(t)
   404  	a := New(Options{})
   405  
   406  	var carsResource Resource = CarsResource{}
   407  
   408  	var resourcesResource Resource = ResourcesResource{}
   409  
   410  	rr := render.New(render.Options{
   411  		HTMLLayout:   "application.plush.html",
   412  		TemplatesBox: packr.New("../templates", "../templates"),
   413  		Helpers:      map[string]interface{}{},
   414  	})
   415  
   416  	sampleHandler := func(c Context) error {
   417  		c.Set("opts", map[string]interface{}{})
   418  		return c.Render(http.StatusOK, rr.String(`
   419  			1. <%= rootPath() %>
   420  			2. <%= userPath({user_id: 1}) %>
   421  			3. <%= myPeepsPath() %>
   422  			5. <%= carPath({car_id: 1}) %>
   423  			6. <%= newCarPath() %>
   424  			7. <%= editCarPath({car_id: 1}) %>
   425  			8. <%= editCarPath({car_id: 1, other: 12}) %>
   426  			9. <%= rootPath({"some":"variable","other": 12}) %>
   427  			10. <%= rootPath() %>
   428  			11. <%= rootPath({"special/":"12=ss"}) %>
   429  			12. <%= resourcePath({resource_id: 1}) %>
   430  			13. <%= editResourcePath({resource_id: 1}) %>
   431  			14. <%= testPath() %>
   432  			15. <%= testNamePath({name: "myTest"}) %>
   433  			16. <%= paganoPath({id: 1}) %>
   434  		`))
   435  	}
   436  
   437  	a.GET("/", sampleHandler)
   438  	a.GET("/users", sampleHandler)
   439  	a.GET("/users/{user_id}", sampleHandler)
   440  	a.GET("/peeps", sampleHandler).Name("myPeeps")
   441  	a.Resource("/car", carsResource)
   442  	a.Resource("/resources", resourcesResource)
   443  	a.GET("/test", sampleHandler)
   444  	a.GET("/test/{name}", sampleHandler)
   445  	a.GET("/pagano/{id}", sampleHandler)
   446  
   447  	w := httptest.New(a)
   448  	res := w.HTML("/").Get()
   449  
   450  	r.Equal(http.StatusOK, res.Code)
   451  	r.Contains(res.Body.String(), "1. /")
   452  	r.Contains(res.Body.String(), "2. /users/1")
   453  	r.Contains(res.Body.String(), "3. /peeps")
   454  	r.Contains(res.Body.String(), "5. /car/1")
   455  	r.Contains(res.Body.String(), "6. /car/new")
   456  	r.Contains(res.Body.String(), "7. /car/1/edit")
   457  	r.Contains(res.Body.String(), "8. /car/1/edit/?other=12")
   458  	r.Contains(res.Body.String(), "9. /?other=12&some=variable")
   459  	r.Contains(res.Body.String(), "10. /")
   460  	r.Contains(res.Body.String(), "11. /?special%2F=12%3Dss")
   461  	r.Contains(res.Body.String(), "12. /resources/1")
   462  	r.Contains(res.Body.String(), "13. /resources/1/edit")
   463  	r.Contains(res.Body.String(), "14. /test")
   464  	r.Contains(res.Body.String(), "15. /test/myTest")
   465  	r.Contains(res.Body.String(), "16. /pagano/1")
   466  }
   467  
   468  func Test_App_NamedRoutes_MissingParameter(t *testing.T) {
   469  	r := require.New(t)
   470  	a := New(Options{})
   471  
   472  	rr := render.New(render.Options{
   473  		HTMLLayout:   "application.plush.html",
   474  		TemplatesBox: packr.New("../templates", "../templates"),
   475  		Helpers:      map[string]interface{}{},
   476  	})
   477  
   478  	sampleHandler := func(c Context) error {
   479  		c.Set("opts", map[string]interface{}{})
   480  		return c.Render(http.StatusOK, rr.String(`
   481  			<%= userPath(opts) %>
   482  		`))
   483  	}
   484  
   485  	a.GET("/users/{user_id}", sampleHandler)
   486  	w := httptest.New(a)
   487  	res := w.HTML("/users/1").Get()
   488  
   489  	r.Equal(http.StatusInternalServerError, res.Code)
   490  	r.Contains(res.Body.String(), "missing parameters for /users/{user_id}")
   491  }
   492  
   493  func Test_Resource(t *testing.T) {
   494  	r := require.New(t)
   495  
   496  	type trs struct {
   497  		Method string
   498  		Path   string
   499  		Result string
   500  	}
   501  
   502  	tests := []trs{
   503  		{
   504  			Method: "GET",
   505  			Path:   "",
   506  			Result: "list",
   507  		},
   508  		{
   509  			Method: "GET",
   510  			Path:   "/new",
   511  			Result: "new",
   512  		},
   513  		{
   514  			Method: "GET",
   515  			Path:   "/1",
   516  			Result: "show 1",
   517  		},
   518  		{
   519  			Method: "GET",
   520  			Path:   "/1/edit",
   521  			Result: "edit 1",
   522  		},
   523  		{
   524  			Method: "POST",
   525  			Path:   "",
   526  			Result: "create",
   527  		},
   528  		{
   529  			Method: "PUT",
   530  			Path:   "/1",
   531  			Result: "update 1",
   532  		},
   533  		{
   534  			Method: "DELETE",
   535  			Path:   "/1",
   536  			Result: "destroy 1",
   537  		},
   538  	}
   539  
   540  	a := New(Options{})
   541  	a.Resource("/users", &userResource{})
   542  	a.Resource("/api/v1/users", &userResource{})
   543  
   544  	ts := httptest.NewServer(a)
   545  	defer ts.Close()
   546  
   547  	c := http.Client{}
   548  	for _, path := range []string{"/users", "/api/v1/users"} {
   549  		for _, test := range tests {
   550  			u := ts.URL + path + test.Path
   551  			req, err := http.NewRequest(test.Method, u, nil)
   552  			r.NoError(err)
   553  			res, err := c.Do(req)
   554  			r.NoError(err)
   555  			b, err := ioutil.ReadAll(res.Body)
   556  			r.NoError(err)
   557  			r.Equal(test.Result, string(b))
   558  		}
   559  	}
   560  
   561  }
   562  
   563  type paramKeyResource struct {
   564  	*userResource
   565  }
   566  
   567  func (paramKeyResource) ParamKey() string {
   568  	return "bazKey"
   569  }
   570  
   571  func Test_Resource_ParamKey(t *testing.T) {
   572  	r := require.New(t)
   573  	fr := &paramKeyResource{&userResource{}}
   574  	a := New(Options{})
   575  	a.Resource("/foo", fr)
   576  	rt := a.Routes()
   577  	paths := []string{}
   578  	for _, rr := range rt {
   579  		paths = append(paths, rr.Path)
   580  	}
   581  	r.Contains(paths, "/foo/{bazKey}/edit/")
   582  }
   583  
   584  type mwResource struct {
   585  	WebResource
   586  }
   587  
   588  func (mwResource) Use() []MiddlewareFunc {
   589  	var mw []MiddlewareFunc
   590  
   591  	mw = append(mw, func(next Handler) Handler {
   592  		return func(c Context) error {
   593  			if c.Param("good") == "" {
   594  				return fmt.Errorf("not good")
   595  			}
   596  			return next(c)
   597  		}
   598  	})
   599  
   600  	return mw
   601  }
   602  
   603  func (m mwResource) List(c Context) error {
   604  	return c.Render(http.StatusOK, render.String("southern harmony and the musical companion"))
   605  }
   606  
   607  func Test_Resource_MW(t *testing.T) {
   608  	r := require.New(t)
   609  	fr := mwResource{}
   610  	a := New(Options{})
   611  	a.Resource("/foo", fr)
   612  
   613  	w := httptest.New(a)
   614  	res := w.HTML("/foo?good=true").Get()
   615  	r.Equal(http.StatusOK, res.Code)
   616  	r.Contains(res.Body.String(), "southern harmony")
   617  
   618  	res = w.HTML("/foo").Get()
   619  	r.Equal(http.StatusInternalServerError, res.Code)
   620  
   621  	r.NotContains(res.Body.String(), "southern harmony")
   622  }
   623  
   624  type userResource struct{}
   625  
   626  func (u *userResource) List(c Context) error {
   627  	return c.Render(http.StatusOK, render.String("list"))
   628  }
   629  
   630  func (u *userResource) Show(c Context) error {
   631  	return c.Render(http.StatusOK, render.String(`show <%=params["user_id"] %>`))
   632  }
   633  
   634  func (u *userResource) New(c Context) error {
   635  	return c.Render(http.StatusOK, render.String("new"))
   636  }
   637  
   638  func (u *userResource) Create(c Context) error {
   639  	return c.Render(http.StatusOK, render.String("create"))
   640  }
   641  
   642  func (u *userResource) Edit(c Context) error {
   643  	return c.Render(http.StatusOK, render.String(`edit <%=params["user_id"] %>`))
   644  }
   645  
   646  func (u *userResource) Update(c Context) error {
   647  	return c.Render(http.StatusOK, render.String(`update <%=params["user_id"] %>`))
   648  }
   649  
   650  func (u *userResource) Destroy(c Context) error {
   651  	return c.Render(http.StatusOK, render.String(`destroy <%=params["user_id"] %>`))
   652  }
   653  
   654  func Test_ResourceOnResource(t *testing.T) {
   655  	r := require.New(t)
   656  
   657  	a := New(Options{})
   658  	ur := a.Resource("/users", &userResource{})
   659  	ur.Resource("/people", &userResource{})
   660  
   661  	ts := httptest.NewServer(a)
   662  	defer ts.Close()
   663  
   664  	type trs struct {
   665  		Method string
   666  		Path   string
   667  		Result string
   668  	}
   669  	tests := []trs{
   670  		{
   671  			Method: "GET",
   672  			Path:   "/people",
   673  			Result: "list",
   674  		},
   675  		{
   676  			Method: "GET",
   677  			Path:   "/people/new",
   678  			Result: "new",
   679  		},
   680  		{
   681  			Method: "GET",
   682  			Path:   "/people/1",
   683  			Result: "show 1",
   684  		},
   685  		{
   686  			Method: "GET",
   687  			Path:   "/people/1/edit",
   688  			Result: "edit 1",
   689  		},
   690  		{
   691  			Method: "POST",
   692  			Path:   "/people",
   693  			Result: "create",
   694  		},
   695  		{
   696  			Method: "PUT",
   697  			Path:   "/people/1",
   698  			Result: "update 1",
   699  		},
   700  		{
   701  			Method: "DELETE",
   702  			Path:   "/people/1",
   703  			Result: "destroy 1",
   704  		},
   705  	}
   706  	c := http.Client{}
   707  	for _, test := range tests {
   708  		u := ts.URL + path.Join("/users/42", test.Path)
   709  		req, err := http.NewRequest(test.Method, u, nil)
   710  		r.NoError(err)
   711  		res, err := c.Do(req)
   712  		r.NoError(err)
   713  		b, err := ioutil.ReadAll(res.Body)
   714  		r.NoError(err)
   715  		r.Equal(test.Result, string(b))
   716  	}
   717  
   718  }
   719  
   720  func Test_buildRouteName(t *testing.T) {
   721  	r := require.New(t)
   722  	cases := map[string]string{
   723  		"/":                                    "root",
   724  		"/users":                               "users",
   725  		"/users/new":                           "newUsers",
   726  		"/users/{user_id}":                     "user",
   727  		"/users/{user_id}/children":            "userChildren",
   728  		"/users/{user_id}/children/{child_id}": "userChild",
   729  		"/users/{user_id}/children/new":        "newUserChildren",
   730  		"/users/{user_id}/children/{child_id}/build": "userChildBuild",
   731  		"/admin/planes":                         "adminPlanes",
   732  		"/admin/planes/{plane_id}":              "adminPlane",
   733  		"/admin/planes/{plane_id}/edit":         "editAdminPlane",
   734  		"/test":                                 "test",
   735  		"/tests/{name}":                         "testName",
   736  		"/tests/{name_id}/cases/{case_id}":      "testNameIdCase",
   737  		"/tests/{name_id}/cases/{case_id}/edit": "editTestNameIdCase",
   738  	}
   739  
   740  	a := New(Options{})
   741  
   742  	for input, result := range cases {
   743  		fResult := a.RouteNamer.NameRoute(input)
   744  		r.Equal(result, fResult, input)
   745  	}
   746  
   747  	a = New(Options{Prefix: "/test"})
   748  	cases = map[string]string{
   749  		"/test":       "test",
   750  		"/test/users": "testUsers",
   751  	}
   752  
   753  	for input, result := range cases {
   754  		fResult := a.RouteNamer.NameRoute(input)
   755  		r.Equal(result, fResult, input)
   756  	}
   757  }
   758  
   759  func Test_CatchAll_Route(t *testing.T) {
   760  	r := require.New(t)
   761  	rr := render.New(render.Options{})
   762  
   763  	a := New(Options{})
   764  	a.GET("/{name:.+}", func(c Context) error {
   765  		name := c.Param("name")
   766  		return c.Render(http.StatusOK, rr.String(name))
   767  	})
   768  
   769  	w := httptest.New(a)
   770  	res := w.HTML("/john").Get()
   771  
   772  	r.Contains(res.Body.String(), "john")
   773  }
   774  
   775  func Test_Router_Matches_Trailing_Slash(t *testing.T) {
   776  	table := []struct {
   777  		mapped   string
   778  		browser  string
   779  		expected string
   780  	}{
   781  		{"/foo", "/foo", "/foo/"},
   782  		{"/foo", "/foo/", "/foo/"},
   783  		{"/foo/", "/foo", "/foo/"},
   784  		{"/foo/", "/foo/", "/foo/"},
   785  		{"/index.html", "/index.html", "/index.html/"},
   786  		{"/foo.gif", "/foo.gif", "/foo.gif/"},
   787  		{"/{img}", "/foo.png", "/foo.png/"},
   788  	}
   789  
   790  	for _, tt := range table {
   791  		t.Run(tt.mapped+"|"+tt.browser, func(st *testing.T) {
   792  			r := require.New(st)
   793  
   794  			app := New(Options{
   795  				PreWares: []PreWare{
   796  					func(h http.Handler) http.Handler {
   797  						var f http.HandlerFunc = func(res http.ResponseWriter, req *http.Request) {
   798  							path := req.URL.Path
   799  							req.URL.Path = strings.TrimSuffix(path, "/")
   800  							r.False(strings.HasSuffix(req.URL.Path, "/"))
   801  							h.ServeHTTP(res, req)
   802  						}
   803  						return f
   804  					},
   805  				},
   806  			})
   807  			app.GET(tt.mapped, func(c Context) error {
   808  				return c.Render(http.StatusOK, render.String(c.Request().URL.Path))
   809  			})
   810  
   811  			w := httptest.New(app)
   812  			res := w.HTML(tt.browser).Get()
   813  
   814  			r.Equal(http.StatusOK, res.Code)
   815  			r.Equal(tt.expected, res.Body.String())
   816  		})
   817  	}
   818  }