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 := ¶mKeyResource{&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 }