goyave.dev/goyave/v4@v4.4.11/router_test.go (about) 1 package goyave 2 3 import ( 4 "io" 5 "net/http" 6 "net/http/httptest" 7 "strconv" 8 "testing" 9 10 "goyave.dev/goyave/v4/config" 11 "goyave.dev/goyave/v4/cors" 12 ) 13 14 type RouterTestSuite struct { 15 TestSuite 16 middlewareExecuted bool 17 } 18 19 func createRouterTestRequest(url string) (*Request, *Response) { 20 rawRequest := httptest.NewRequest("GET", url, nil) 21 request := &Request{ 22 httpRequest: rawRequest, 23 Params: map[string]string{"resource": url}, 24 } 25 response := newResponse(httptest.NewRecorder(), nil) 26 return request, response 27 } 28 29 func (suite *RouterTestSuite) routerTestMiddleware(handler Handler) Handler { 30 return func(response *Response, request *Request) { 31 suite.middlewareExecuted = true 32 handler(response, request) 33 } 34 } 35 36 func (suite *RouterTestSuite) createOrderedTestMiddleware(result *string, str string) Middleware { 37 return func(next Handler) Handler { 38 return func(response *Response, r *Request) { 39 *result += str 40 next(response, r) 41 } 42 } 43 } 44 45 func (suite *RouterTestSuite) TestNewRouter() { 46 router := NewRouter() 47 suite.NotNil(router) 48 suite.NotNil(router.regexCache) 49 suite.Nil(router.parent) 50 suite.Empty(router.prefix) 51 suite.False(router.hasCORSMiddleware) 52 suite.Equal(0, len(router.middleware)) 53 suite.Equal(3, len(router.globalMiddleware.middleware)) 54 suite.NotEmpty(router.statusHandlers) 55 } 56 57 func (suite *RouterTestSuite) TestClearRegexCache() { 58 router := NewRouter() 59 subrouter := router.Subrouter("/sub") 60 router.ClearRegexCache() 61 suite.Nil(router.regexCache) 62 suite.Nil(subrouter.regexCache) 63 } 64 65 func (suite *RouterTestSuite) TestRouterRegisterRoute() { 66 router := NewRouter() 67 route := router.Route("GET", "/uri", func(resp *Response, r *Request) {}) 68 suite.Contains(router.routes, route) 69 suite.Equal(router, route.parent) 70 71 route = router.Route("GET", "/", func(resp *Response, r *Request) {}) 72 suite.Equal("/", route.uri) 73 suite.Equal(router, route.parent) 74 75 route = router.Route("GET|POST", "/", func(resp *Response, r *Request) {}) 76 suite.Equal([]string{"GET", "POST", "HEAD"}, route.methods) 77 suite.Equal(router, route.parent) 78 79 subrouter := router.Subrouter("/sub") 80 route = subrouter.Route("GET", "/", func(resp *Response, r *Request) {}) 81 suite.Equal("", route.uri) 82 83 group := router.Group() 84 route = group.Route("GET", "/", func(resp *Response, r *Request) {}) 85 suite.Equal("/", route.uri) 86 87 group2 := router.Subrouter("/") 88 route = group2.Route("GET", "/", func(resp *Response, r *Request) {}) 89 suite.Equal("/", route.uri) 90 } 91 92 func (suite *RouterTestSuite) TestRouterMiddleware() { 93 router := NewRouter() 94 suite.Equal(0, len(router.middleware)) 95 router.Middleware(suite.routerTestMiddleware) 96 suite.Equal(1, len(router.middleware)) 97 } 98 99 func (suite *RouterTestSuite) TestSubRouter() { 100 router := NewRouter() 101 suite.Equal(0, len(router.middleware)) 102 router.Middleware(suite.routerTestMiddleware) 103 suite.Equal(1, len(router.middleware)) 104 105 subrouter := router.Subrouter("/sub") 106 suite.Contains(router.subrouters, subrouter) 107 suite.Equal(0, len(subrouter.middleware)) // Middleware inherited, not copied 108 suite.Equal(router.globalMiddleware.middleware, subrouter.globalMiddleware.middleware) 109 suite.Equal(len(router.statusHandlers), len(subrouter.statusHandlers)) 110 111 subrouter.Middleware(suite.routerTestMiddleware) 112 suite.Equal(1, len(router.middleware)) 113 suite.Equal(1, len(subrouter.middleware)) 114 115 subrouter.GlobalMiddleware(suite.routerTestMiddleware) 116 suite.Equal(4, len(router.globalMiddleware.middleware)) 117 suite.Equal(router.globalMiddleware.middleware, subrouter.globalMiddleware.middleware) 118 119 router = NewRouter() 120 subrouter = router.Subrouter("/") 121 suite.Empty(subrouter.prefix) 122 } 123 124 func (suite *RouterTestSuite) TestCleanStaticPath() { 125 suite.Equal("config/index.html", cleanStaticPath("config", "index.html")) 126 suite.Equal("config/index.html", cleanStaticPath("config", "")) 127 suite.Equal("config/defaults.json", cleanStaticPath("config", "defaults.json")) 128 suite.Equal("resources/lang/en-US/locale.json", cleanStaticPath("resources", "lang/en-US/locale.json")) 129 suite.Equal("resources/lang/en-US/locale.json", cleanStaticPath("resources", "/lang/en-US/locale.json")) 130 suite.Equal("resources/img/logo/index.html", cleanStaticPath("resources", "img/logo")) 131 suite.Equal("resources/img/logo/index.html", cleanStaticPath("resources", "img/logo/")) 132 suite.Equal("resources/img/index.html", cleanStaticPath("resources", "img")) 133 suite.Equal("resources/img/index.html", cleanStaticPath("resources", "img/")) 134 } 135 136 func (suite *RouterTestSuite) TestStaticHandler() { 137 request, response := createRouterTestRequest("/config.test.json") 138 handler := staticHandler("config", false) 139 handler(response, request) 140 result := response.responseWriter.(*httptest.ResponseRecorder).Result() 141 suite.Equal(200, result.StatusCode) 142 suite.Equal("application/json", result.Header.Get("Content-Type")) 143 suite.Equal("inline", result.Header.Get("Content-Disposition")) 144 145 body, err := io.ReadAll(result.Body) 146 if err != nil { 147 panic(err) 148 } 149 result.Body.Close() 150 151 suite.True(len(body) > 0) 152 153 request, response = createRouterTestRequest("/doesn'texist") 154 handler = staticHandler("config", false) 155 handler(response, request) 156 result = response.responseWriter.(*httptest.ResponseRecorder).Result() 157 suite.Equal(200, result.StatusCode) // Not written yet 158 suite.Equal(http.StatusNotFound, response.GetStatus()) 159 160 body, err = io.ReadAll(result.Body) 161 if err != nil { 162 panic(err) 163 } 164 result.Body.Close() 165 166 suite.Equal(0, len(body)) 167 168 request, response = createRouterTestRequest("/config.test.json") 169 handler = staticHandler("config", true) 170 handler(response, request) 171 result = response.responseWriter.(*httptest.ResponseRecorder).Result() 172 suite.Equal(200, result.StatusCode) 173 suite.Equal("application/json", result.Header.Get("Content-Type")) 174 suite.Equal("attachment; filename=\"config.test.json\"", result.Header.Get("Content-Disposition")) 175 176 body, err = io.ReadAll(result.Body) 177 if err != nil { 178 panic(err) 179 } 180 result.Body.Close() 181 182 suite.True(len(body) > 0) 183 } 184 185 func (suite *RouterTestSuite) TestRequestHandler() { 186 rawRequest := httptest.NewRequest("GET", "/uri", nil) 187 writer := httptest.NewRecorder() 188 router := NewRouter() 189 190 route := &Route{} 191 var tmp *Route 192 route.handler = func(response *Response, request *Request) { 193 suite.NotNil(request.Extra) 194 tmp = request.Route() 195 response.String(200, "Hello world") 196 } 197 match := &routeMatch{route: route} 198 router.requestHandler(match, writer, rawRequest) 199 suite.Equal(route, tmp) 200 201 result := writer.Result() 202 body, err := io.ReadAll(result.Body) 203 if err != nil { 204 panic(err) 205 } 206 result.Body.Close() 207 suite.Equal(200, result.StatusCode) 208 suite.Equal("Hello world", string(body)) 209 210 writer = httptest.NewRecorder() 211 router = NewRouter() 212 router.Middleware(suite.routerTestMiddleware) 213 214 match = &routeMatch{ 215 route: &Route{ 216 handler: func(response *Response, request *Request) {}, 217 parent: router, 218 }, 219 } 220 router.requestHandler(match, writer, rawRequest) 221 222 result = writer.Result() 223 body, err = io.ReadAll(result.Body) 224 if err != nil { 225 panic(err) 226 } 227 result.Body.Close() 228 suite.Equal(204, result.StatusCode) 229 suite.Equal(0, len(body)) 230 suite.True(suite.middlewareExecuted) 231 suite.middlewareExecuted = false 232 233 writer = httptest.NewRecorder() 234 router = NewRouter() 235 match = &routeMatch{ 236 route: &Route{ 237 handler: func(response *Response, request *Request) { 238 response.Status(http.StatusNotFound) 239 }, 240 }, 241 } 242 router.requestHandler(match, writer, rawRequest) 243 244 result = writer.Result() 245 body, err = io.ReadAll(result.Body) 246 if err != nil { 247 panic(err) 248 } 249 result.Body.Close() 250 suite.Equal(http.StatusNotFound, result.StatusCode) 251 suite.Equal("{\"error\":\""+http.StatusText(http.StatusNotFound)+"\"}\n", string(body)) 252 } 253 254 func (suite *RouterTestSuite) TestCORS() { 255 router := NewRouter() 256 suite.Nil(router.corsOptions) 257 258 router.CORS(cors.Default()) 259 260 suite.NotNil(router.corsOptions) 261 suite.True(router.hasCORSMiddleware) 262 263 route := router.registerRoute("GET", "/cors", helloHandler) 264 suite.Equal([]string{"GET", "OPTIONS", "HEAD"}, route.methods) 265 266 match := routeMatch{currentPath: "/cors"} 267 suite.True(route.match(httptest.NewRequest("OPTIONS", "/cors", nil), &match)) 268 match = routeMatch{currentPath: "/cors"} 269 suite.True(route.match(httptest.NewRequest("GET", "/cors", nil), &match)) 270 271 writer := httptest.NewRecorder() 272 router.Middleware(func(handler Handler) Handler { 273 return func(response *Response, request *Request) { 274 suite.NotNil(request.corsOptions) 275 suite.NotNil(request.CORSOptions()) 276 handler(response, request) 277 } 278 }) 279 rawRequest := httptest.NewRequest("GET", "/cors", nil) 280 281 match = routeMatch{ 282 route: &Route{ 283 handler: func(response *Response, request *Request) {}, 284 }, 285 } 286 router.requestHandler(&match, writer, rawRequest) 287 } 288 289 func (suite *RouterTestSuite) TestCORSSubrouter() { 290 router := NewRouter() 291 suite.Nil(router.corsOptions) 292 293 options := cors.Default() 294 group := router.Group() 295 group.CORS(options) 296 group.registerRoute("GET", "/cors", helloHandler) 297 match := routeMatch{currentPath: "/cors"} 298 suite.True(router.match(httptest.NewRequest("OPTIONS", "/cors", nil), &match)) 299 300 writer := httptest.NewRecorder() 301 executed := false 302 group.Middleware(func(handler Handler) Handler { 303 return func(response *Response, request *Request) { 304 executed = true 305 suite.NotNil(request.corsOptions) 306 suite.NotNil(request.CORSOptions()) 307 suite.Same(options, request.corsOptions) 308 handler(response, request) 309 } 310 }) 311 312 rawRequest := httptest.NewRequest("GET", "/cors", nil) 313 router.requestHandler(&match, writer, rawRequest) 314 suite.True(executed) 315 316 // Method not allowed 317 executed = false 318 rawRequest = httptest.NewRequest("POST", "/cors", nil) 319 router.requestHandler(&match, writer, rawRequest) 320 suite.True(executed) 321 } 322 323 func (suite *RouterTestSuite) TestCORSNotFound() { 324 // Should use main router settings 325 router := NewRouter() 326 rootOptions := cors.Default() 327 router.CORS(rootOptions) 328 329 options := cors.Default() 330 group := router.Group() 331 group.CORS(options) 332 group.registerRoute("GET", "/cors", helloHandler) 333 match := routeMatch{currentPath: "/notaroute"} 334 router.match(httptest.NewRequest("GET", "/notaroute", nil), &match) 335 336 writer := httptest.NewRecorder() 337 executed := false 338 match.route = newRoute(func(response *Response, request *Request) { 339 executed = true 340 suite.Same(rootOptions, request.corsOptions) 341 }) 342 343 rawRequest := httptest.NewRequest("GET", "/notaroute", nil) 344 router.requestHandler(&match, writer, rawRequest) 345 suite.True(executed) 346 } 347 348 func (suite *RouterTestSuite) TestPanicStatusHandler() { 349 request, response := createRouterTestRequest("/uri") 350 response.err = "random error" 351 PanicStatusHandler(response, request) 352 result := response.responseWriter.(*httptest.ResponseRecorder).Result() 353 suite.Equal(500, result.StatusCode) 354 result.Body.Close() 355 } 356 357 func (suite *RouterTestSuite) TestErrorStatusHandler() { 358 request, response := createRouterTestRequest("/uri") 359 response.Status(http.StatusNotFound) 360 ErrorStatusHandler(response, request) 361 result := response.responseWriter.(*httptest.ResponseRecorder).Result() 362 suite.Equal(http.StatusNotFound, result.StatusCode) 363 suite.Equal("application/json; charset=utf-8", result.Header.Get("Content-Type")) 364 365 body, err := io.ReadAll(result.Body) 366 if err != nil { 367 panic(err) 368 } 369 result.Body.Close() 370 suite.Equal("{\"error\":\""+http.StatusText(http.StatusNotFound)+"\"}\n", string(body)) 371 } 372 373 func (suite *RouterTestSuite) TestStatusHandlers() { 374 rawRequest := httptest.NewRequest("GET", "/uri", nil) 375 writer := httptest.NewRecorder() 376 router := NewRouter() 377 router.StatusHandler(func(response *Response, request *Request) { 378 response.String(http.StatusInternalServerError, "An unexpected panic occurred.") 379 }, http.StatusInternalServerError) 380 381 match := &routeMatch{ 382 route: &Route{ 383 handler: func(response *Response, request *Request) { 384 panic("Panic") 385 }, 386 parent: router, 387 }, 388 } 389 router.requestHandler(match, writer, rawRequest) 390 391 result := writer.Result() 392 body, err := io.ReadAll(result.Body) 393 if err != nil { 394 panic(err) 395 } 396 result.Body.Close() 397 suite.Equal(500, result.StatusCode) 398 suite.Equal("An unexpected panic occurred.", string(body)) 399 400 // On subrouters 401 subrouter := router.Subrouter("/sub") 402 writer = httptest.NewRecorder() 403 404 subrouter.requestHandler(match, writer, rawRequest) 405 406 result = writer.Result() 407 body, err = io.ReadAll(result.Body) 408 if err != nil { 409 panic(err) 410 } 411 result.Body.Close() 412 suite.Equal(500, result.StatusCode) 413 suite.Equal("An unexpected panic occurred.", string(body)) 414 415 // Multiple statuses 416 writer = httptest.NewRecorder() 417 subrouter.StatusHandler(func(response *Response, request *Request) { 418 response.String(response.GetStatus(), http.StatusText(response.GetStatus())) 419 }, http.StatusBadRequest, http.StatusNotFound) 420 421 match = &routeMatch{ 422 route: &Route{ 423 handler: func(response *Response, request *Request) { 424 response.Status(http.StatusBadRequest) 425 }, 426 }, 427 } 428 subrouter.requestHandler(match, writer, rawRequest) 429 430 result = writer.Result() 431 body, err = io.ReadAll(result.Body) 432 if err != nil { 433 panic(err) 434 } 435 result.Body.Close() 436 suite.Equal(http.StatusBadRequest, result.StatusCode) 437 suite.Equal(http.StatusText(http.StatusBadRequest), string(body)) 438 439 writer = httptest.NewRecorder() 440 441 match = &routeMatch{ 442 route: &Route{ 443 handler: func(response *Response, request *Request) { 444 response.Status(http.StatusNotFound) 445 }, 446 }, 447 } 448 subrouter.requestHandler(match, writer, rawRequest) 449 450 result = writer.Result() 451 body, err = io.ReadAll(result.Body) 452 if err != nil { 453 panic(err) 454 } 455 result.Body.Close() 456 suite.Equal(http.StatusNotFound, result.StatusCode) 457 suite.Equal(http.StatusText(http.StatusNotFound), string(body)) 458 } 459 460 func (suite *RouterTestSuite) TestRouteNoMatch() { 461 rawRequest := httptest.NewRequest("GET", "/uri", nil) 462 writer := httptest.NewRecorder() 463 router := NewRouter() 464 465 match := &routeMatch{route: notFoundRoute} 466 router.requestHandler(match, writer, rawRequest) 467 result := writer.Result() 468 suite.Equal(http.StatusNotFound, result.StatusCode) 469 result.Body.Close() 470 471 writer = httptest.NewRecorder() 472 match = &routeMatch{route: methodNotAllowedRoute} 473 router.requestHandler(match, writer, rawRequest) 474 result = writer.Result() 475 suite.Equal(http.StatusMethodNotAllowed, result.StatusCode) 476 result.Body.Close() 477 } 478 479 func (suite *RouterTestSuite) TestNamedRoutes() { 480 r := NewRouter() 481 route := r.Route("GET", "/uri", func(resp *Response, r *Request) {}) 482 route.Name("get-uri") 483 suite.Equal(route, r.namedRoutes["get-uri"]) 484 suite.Equal(route, r.GetRoute("get-uri")) 485 486 subrouter := r.Subrouter("/sub") 487 suite.Equal(route, subrouter.GetRoute("get-uri")) 488 489 route2 := r.Route("GET", "/other-route", func(resp *Response, r *Request) {}) 490 suite.Panics(func() { 491 route2.Name("get-uri") 492 }) 493 suite.Empty(route2.GetName()) 494 495 // Global router 496 router = r 497 suite.Equal(route, GetRoute("get-uri")) 498 router = nil 499 } 500 501 func (suite *RouterTestSuite) TestMiddleware() { 502 // Test the middleware execution order 503 result := "" 504 middleware := make([]Middleware, 0, 4) 505 for i := 0; i < 4; i++ { 506 middleware = append(middleware, suite.createOrderedTestMiddleware(&result, strconv.Itoa(i+1))) 507 } 508 router := NewRouter() 509 router.Middleware(middleware[0]) 510 router.GlobalMiddleware(suite.createOrderedTestMiddleware(&result, "g1"), suite.createOrderedTestMiddleware(&result, "g2")) 511 512 subrouter := router.Subrouter("/") 513 subrouter.Middleware(middleware[1]) 514 subrouter.GlobalMiddleware(suite.createOrderedTestMiddleware(&result, "g3")) 515 516 handler := func(response *Response, r *Request) { 517 result += "5" 518 } 519 route := subrouter.Route("GET", "/hello", handler).Middleware(middleware[2], middleware[3]) 520 521 rawRequest := httptest.NewRequest("GET", "/hello", nil) 522 match := routeMatch{ 523 route: route, 524 currentPath: rawRequest.URL.Path, 525 } 526 router.requestHandler(&match, httptest.NewRecorder(), rawRequest) 527 528 suite.Equal("g1g2g312345", result) 529 } 530 531 func (suite *RouterTestSuite) TestCoreMiddleware() { 532 // Ensure core middleware is executed on Not Found and Method Not Allowed 533 router := NewRouter() 534 535 match := &routeMatch{ 536 route: newRoute(func(response *Response, request *Request) { 537 panic("Test panic") // Test recover middleware is executed 538 }), 539 } 540 541 writer := httptest.NewRecorder() 542 prev := config.Get("app.debug") 543 config.Set("app.debug", false) 544 router.requestHandler(match, writer, httptest.NewRequest("GET", "/uri", nil)) 545 config.Set("app.debug", prev) 546 547 result := writer.Result() 548 body, err := io.ReadAll(result.Body) 549 if err != nil { 550 panic(err) 551 } 552 result.Body.Close() 553 suite.Equal(500, result.StatusCode) 554 suite.Equal("{\"error\":\"Internal Server Error\"}\n", string(body)) 555 556 lang := "" 557 param := "" 558 match = &routeMatch{ 559 route: newRoute(func(response *Response, request *Request) { 560 // Test lang and parse request 561 lang = request.Lang 562 param = request.String("param") 563 }), 564 } 565 566 writer = httptest.NewRecorder() 567 router.requestHandler(match, writer, httptest.NewRequest("GET", "/uri?param=param", nil)) 568 suite.Equal("en-US", lang) 569 suite.Equal("param", param) 570 571 // Custom middleware shouldn't be executed 572 strResult := "" 573 testMiddleware := suite.createOrderedTestMiddleware(&strResult, "1") 574 router.Middleware(testMiddleware) 575 576 match = &routeMatch{route: notFoundRoute} 577 router.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 578 suite.Empty(strResult) 579 580 strResult = "" 581 match = &routeMatch{route: methodNotAllowedRoute} 582 router.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 583 suite.Empty(strResult) 584 585 // Global middleware should be executed 586 router.GlobalMiddleware(testMiddleware) 587 strResult = "" 588 match = &routeMatch{route: methodNotAllowedRoute} 589 router.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 590 suite.Equal("1", strResult) 591 592 strResult = "" 593 match = &routeMatch{route: notFoundRoute} 594 router.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 595 suite.Equal("1", strResult) 596 597 // On subrouter 598 router.globalMiddleware.middleware = []Middleware{} 599 subrouter := router.Subrouter("/sub") 600 strResult = "" 601 match = &routeMatch{route: notFoundRoute} 602 subrouter.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 603 suite.Empty(strResult) 604 605 strResult = "" 606 match = &routeMatch{route: methodNotAllowedRoute} 607 subrouter.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 608 suite.Empty(strResult) 609 610 router.GlobalMiddleware(testMiddleware) 611 strResult = "" 612 match = &routeMatch{route: methodNotAllowedRoute} 613 subrouter.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 614 suite.Equal("1", strResult) 615 616 strResult = "" 617 match = &routeMatch{route: notFoundRoute} 618 subrouter.requestHandler(match, httptest.NewRecorder(), httptest.NewRequest("GET", "/uri", nil)) 619 suite.Equal("1", strResult) 620 } 621 622 func (suite *RouterTestSuite) TestMiddlewareHolder() { 623 result := "" 624 testMiddleware := suite.createOrderedTestMiddleware(&result, "1") 625 secondTestMiddleware := suite.createOrderedTestMiddleware(&result, "2") 626 627 holder := &middlewareHolder{[]Middleware{testMiddleware, secondTestMiddleware}} 628 handler := holder.applyMiddleware(func(response *Response, r *Request) { 629 result += "3" 630 }) 631 handler(suite.CreateTestResponse(httptest.NewRecorder()), suite.CreateTestRequest(nil)) 632 suite.Equal("123", result) 633 } 634 635 func (suite *RouterTestSuite) TestTrimCurrentPath() { 636 routeMatch := routeMatch{currentPath: "/product/55"} 637 routeMatch.trimCurrentPath("/product") 638 suite.Equal("/55", routeMatch.currentPath) 639 } 640 641 func (suite *RouterTestSuite) TestMatch() { 642 handler := func(response *Response, r *Request) { 643 response.String(http.StatusOK, "Hello") 644 } 645 646 router := NewRouter() 647 router.Route("GET", "/", handler).Name("root") 648 router.Route("GET|POST", "/hello", handler).Name("hello") 649 router.Route("PUT", "/hello", handler).Name("hello.put") 650 router.Route("GET", "/hello/sub", handler).Name("hello.sub") 651 652 productRouter := router.Subrouter("/product") 653 productRouter.Route("GET", "/", handler).Name("product.index") 654 productRouter.Route("GET", "/{id:[0-9]+}", handler).Name("product.show") 655 productRouter.Route("GET", "/{id:[0-9]+}/details", handler).Name("product.show.details") 656 657 userRouter := router.Subrouter("/user") 658 userRouter.Route("GET", "/", handler).Name("user.index") 659 userRouter.Route("GET", "/{id:[0-9]+}", handler).Name("user.show") 660 661 router.Subrouter("/empty") 662 663 match := routeMatch{currentPath: "/"} 664 suite.True(router.match(httptest.NewRequest("GET", "/", nil), &match)) 665 suite.Equal(router.GetRoute("root"), match.route) 666 667 match = routeMatch{currentPath: "/hello"} 668 suite.True(router.match(httptest.NewRequest("GET", "/hello", nil), &match)) 669 suite.Equal(router.GetRoute("hello"), match.route) 670 671 match = routeMatch{currentPath: "/hello/sub"} 672 suite.True(router.match(httptest.NewRequest("GET", "/hello/sub", nil), &match)) 673 suite.Equal(router.GetRoute("hello.sub"), match.route) 674 675 match = routeMatch{currentPath: "/product"} 676 suite.True(router.match(httptest.NewRequest("GET", "/product", nil), &match)) 677 suite.Equal(router.GetRoute("product.index"), match.route) 678 679 match = routeMatch{currentPath: "/product/5"} 680 suite.True(router.match(httptest.NewRequest("GET", "/product/5", nil), &match)) 681 suite.Equal(router.GetRoute("product.show"), match.route) 682 suite.Equal("5", match.parameters["id"]) 683 684 match = routeMatch{currentPath: "/product/5/details"} 685 suite.True(router.match(httptest.NewRequest("GET", "/product/5/details", nil), &match)) 686 suite.Equal(router.GetRoute("product.show.details"), match.route) 687 suite.Equal("5", match.parameters["id"]) 688 689 match = routeMatch{currentPath: "/user"} 690 suite.True(router.match(httptest.NewRequest("GET", "/user", nil), &match)) 691 suite.Equal(router.GetRoute("user.index"), match.route) 692 693 match = routeMatch{currentPath: "/user/42"} 694 suite.True(router.match(httptest.NewRequest("GET", "/user/42", nil), &match)) 695 suite.Equal(router.GetRoute("user.show"), match.route) 696 suite.Equal("42", match.parameters["id"]) 697 698 match = routeMatch{currentPath: "/product/notaroute"} 699 suite.False(router.match(httptest.NewRequest("GET", "/product/notaroute", nil), &match)) 700 suite.Equal(notFoundRoute, match.route) 701 702 match = routeMatch{currentPath: "/empty"} 703 suite.False(router.match(httptest.NewRequest("GET", "/empty", nil), &match)) 704 suite.Equal(notFoundRoute, match.route) 705 706 match = routeMatch{currentPath: "/product"} 707 suite.True(router.match(httptest.NewRequest("DELETE", "/product", nil), &match)) 708 suite.Equal(methodNotAllowedRoute, match.route) 709 710 // ------------ 711 712 paramSubrouter := router.Subrouter("/{param}") 713 route := paramSubrouter.Route("GET", "/{subparam}", handler).Name("param.name") 714 match = routeMatch{currentPath: "/name/surname"} 715 suite.True(router.match(httptest.NewRequest("GET", "/name/surname", nil), &match)) 716 suite.Equal(route, match.route) 717 suite.Equal("name", match.parameters["param"]) 718 suite.Equal("surname", match.parameters["subparam"]) 719 720 // ------------ 721 722 match = routeMatch{currentPath: "/user/42"} 723 suite.False(productRouter.match(httptest.NewRequest("GET", "/user/42", nil), &match)) 724 match = routeMatch{currentPath: "/product/42"} 725 suite.True(productRouter.match(httptest.NewRequest("GET", "/product/42", nil), &match)) 726 suite.Equal(router.GetRoute("product.show"), match.route) 727 suite.Equal("42", match.parameters["id"]) 728 729 match = routeMatch{currentPath: "/user/42/extra"} 730 suite.False(userRouter.match(httptest.NewRequest("GET", "/user/42/extra", nil), &match)) 731 } 732 733 func (suite *RouterTestSuite) TestScheme() { 734 // From HTTP to HTTPS 735 protocol = "https" 736 config.Set("server.protocol", "https") 737 738 router := NewRouter() 739 740 recorder := httptest.NewRecorder() 741 router.ServeHTTP(recorder, httptest.NewRequest("GET", "http://localhost:443/test?param=1", nil)) 742 result := recorder.Result() 743 body, err := io.ReadAll(result.Body) 744 suite.Nil(err) 745 result.Body.Close() 746 747 suite.Equal(http.StatusPermanentRedirect, result.StatusCode) 748 suite.Equal("<a href=\"https://127.0.0.1:1236/test?param=1\">Permanent Redirect</a>.\n\n", string(body)) 749 750 // From HTTPS to HTTP 751 config.Set("server.protocol", "http") 752 protocol = "http" 753 754 recorder = httptest.NewRecorder() 755 router.ServeHTTP(recorder, httptest.NewRequest("GET", "https://localhost:80/test?param=1", nil)) 756 result = recorder.Result() 757 body, err = io.ReadAll(result.Body) 758 suite.Nil(err) 759 result.Body.Close() 760 761 suite.Equal(http.StatusPermanentRedirect, result.StatusCode) 762 suite.Equal("<a href=\"http://127.0.0.1:1235/test?param=1\">Permanent Redirect</a>.\n\n", string(body)) 763 764 // Only URI 765 recorder = httptest.NewRecorder() 766 router.ServeHTTP(recorder, httptest.NewRequest("GET", "/test?param=1", nil)) 767 result = recorder.Result() 768 body, err = io.ReadAll(result.Body) 769 suite.Nil(err) 770 result.Body.Close() 771 772 suite.Equal(http.StatusNotFound, result.StatusCode) 773 suite.Equal("{\"error\":\"Not Found\"}\n", string(body)) 774 } 775 776 func (suite *RouterTestSuite) TestConflictingRoutes() { 777 // Test subrouter has priority over routes 778 handler := func(response *Response, request *Request) { 779 response.Status(200) 780 } 781 router := NewRouter() 782 783 subrouter := router.Subrouter("/product") 784 routeSub := subrouter.Route("GET", "/{id:[0-9]+}", handler) 785 786 router.Route("GET", "/product/{id:[0-9]+}", handler) 787 788 req := httptest.NewRequest("GET", "/product/2", nil) 789 match := routeMatch{currentPath: req.URL.Path} 790 router.match(req, &match) 791 792 suite.Equal(routeSub, match.route) 793 794 // Test when route not in subrouter but first segment matches 795 // Should not match 796 router.Route("GET", "/product/test", handler) 797 798 req = httptest.NewRequest("GET", "/product/test", nil) 799 match = routeMatch{currentPath: req.URL.Path} 800 router.match(req, &match) 801 802 suite.Equal(notFoundRoute, match.route) 803 } 804 805 func (suite *RouterTestSuite) TestSubrouterEmptyPrefix() { 806 result := "" 807 handler := func(resp *Response, r *Request) {} 808 router := NewRouter() 809 810 productRouter := router.Subrouter("/product") 811 productRouter.Route("GET", "/", handler).Name("product.index") 812 productRouter.Route("GET", "/{id:[0-9]+}", handler).Name("product.show") 813 productRouter.Route("POST", "/hardpath", handler).Name("product.hardpath.post") 814 productRouter.Route("GET", "/conflict", handler).Name("product.conflict") 815 816 // This route group has an empty prefix, the full path is identical to productRouter. 817 // However this group has a middleware and some conflicting routes with productRouter. 818 // Conflict should be resolved and both routes should be able to match. 819 groupProductRouter := productRouter.Subrouter("/") 820 groupProductRouter.Middleware(suite.createOrderedTestMiddleware(&result, "1")) 821 groupProductRouter.Route("POST", "/", handler).Name("product.store") 822 groupProductRouter.Route("GET", "/hardpath", handler).Name("product.hardpath.get") 823 groupProductRouter.Route("PUT", "/{id:[0-9]+}", handler).Name("product.update") 824 groupProductRouter.Route("GET", "/conflict", handler).Name("product.conflict.group") 825 groupProductRouter.Route("POST", "/method", handler).Name("product.method") 826 827 req := httptest.NewRequest("GET", "/product", nil) 828 match := routeMatch{currentPath: req.URL.Path} 829 router.match(req, &match) 830 suite.Equal("product.index", match.route.name) 831 router.requestHandler(&match, httptest.NewRecorder(), req) 832 suite.Empty(result) 833 result = "" 834 835 req = httptest.NewRequest("POST", "/product", nil) 836 match = routeMatch{currentPath: req.URL.Path} 837 router.match(req, &match) 838 suite.Equal("product.store", match.route.name) 839 router.requestHandler(&match, httptest.NewRecorder(), req) 840 suite.Equal("1", result) 841 result = "" 842 843 req = httptest.NewRequest("GET", "/product/hardpath", nil) 844 match = routeMatch{currentPath: req.URL.Path} 845 router.match(req, &match) 846 suite.Equal("product.hardpath.get", match.route.name) 847 router.requestHandler(&match, httptest.NewRecorder(), req) 848 suite.Equal("1", result) 849 result = "" 850 851 req = httptest.NewRequest("POST", "/product/hardpath", nil) 852 match = routeMatch{currentPath: req.URL.Path} 853 router.match(req, &match) 854 suite.Equal("product.hardpath.post", match.route.name) 855 router.requestHandler(&match, httptest.NewRecorder(), req) 856 suite.Empty(result) 857 result = "" 858 859 req = httptest.NewRequest("GET", "/product/42", nil) 860 match = routeMatch{currentPath: req.URL.Path} 861 router.match(req, &match) 862 suite.Equal("product.show", match.route.name) 863 router.requestHandler(&match, httptest.NewRecorder(), req) 864 suite.Empty(result) 865 result = "" 866 867 req = httptest.NewRequest("PUT", "/product/42", nil) 868 match = routeMatch{currentPath: req.URL.Path} 869 router.match(req, &match) 870 suite.Equal("product.update", match.route.name) 871 router.requestHandler(&match, httptest.NewRecorder(), req) 872 suite.Equal("1", result) 873 result = "" 874 875 req = httptest.NewRequest("GET", "/product/conflict", nil) 876 match = routeMatch{currentPath: req.URL.Path} 877 router.match(req, &match) 878 suite.Equal("product.conflict.group", match.route.name) 879 880 req = httptest.NewRequest("GET", "/product/method", nil) 881 match = routeMatch{currentPath: req.URL.Path} 882 router.match(req, &match) 883 suite.Equal(methodNotAllowedRoute, match.route) 884 } 885 886 func (suite *RouterTestSuite) TestChainedWriterCloseOnPanic() { 887 result := "" 888 testWr := &testWriter{nil, &result, "0", false} 889 890 suite.RunServer(func(router *Router) { 891 router.Middleware(func(next Handler) Handler { 892 return func(response *Response, r *Request) { 893 testWr.Writer = response.Writer() 894 response.SetWriter(testWr) 895 896 next(response, r) 897 } 898 }) 899 router.Route("GET", "/panic", func(response *Response, req *Request) { 900 panic("chained writer panic") 901 }) 902 }, func() { 903 resp, err := suite.Get("/panic", nil) 904 if err != nil { 905 panic(err) 906 } 907 defer resp.Body.Close() 908 909 suite.Equal(500, resp.StatusCode) 910 suite.True(testWr.closed) 911 }) 912 913 suite.True(testWr.closed) 914 } 915 916 func (suite *RouterTestSuite) TestMethodRouteRegistration() { 917 router := NewRouter() 918 route := router.Get("/uri", func(resp *Response, r *Request) {}) 919 suite.Equal([]string{"GET", "HEAD"}, route.methods) 920 921 route = router.Post("/uri", func(resp *Response, r *Request) {}) 922 suite.Equal([]string{"POST"}, route.methods) 923 924 route = router.Put("/uri", func(resp *Response, r *Request) {}) 925 suite.Equal([]string{"PUT"}, route.methods) 926 927 route = router.Patch("/uri", func(resp *Response, r *Request) {}) 928 suite.Equal([]string{"PATCH"}, route.methods) 929 930 route = router.Delete("/uri", func(resp *Response, r *Request) {}) 931 suite.Equal([]string{"DELETE"}, route.methods) 932 933 route = router.Options("/uri", func(resp *Response, r *Request) {}) 934 suite.Equal([]string{"OPTIONS"}, route.methods) 935 } 936 937 func (suite *RouterTestSuite) TestFinalizeHijacked() { 938 recorder := &hijackableRecorder{httptest.NewRecorder()} 939 req := httptest.NewRequest(http.MethodGet, "/hijack", nil) 940 request := suite.CreateTestRequest(req) 941 resp := newResponse(recorder, req) 942 943 c, _, err := resp.Hijack() 944 if err != nil { 945 suite.Fail(err.Error()) 946 } 947 defer c.Close() 948 949 router := NewRouter() 950 router.finalize(resp, request) 951 952 suite.False(resp.wroteHeader) 953 } 954 955 func (suite *RouterTestSuite) TestGroup() { 956 router := NewRouter() 957 group := router.Group() 958 suite.Empty(group.prefix) 959 } 960 961 func (suite *RouterTestSuite) TestGetRoutes() { 962 router := NewRouter() 963 router.Get("/test", func(r1 *Response, r2 *Request) {}) 964 router.Post("/test", func(r1 *Response, r2 *Request) {}) 965 966 routes := router.GetRoutes() 967 suite.Len(routes, 2) 968 suite.NotSame(router.routes, routes) 969 } 970 971 func (suite *RouterTestSuite) TestGetSubrouters() { 972 router := NewRouter() 973 router.Subrouter("/test") 974 router.Subrouter("/other") 975 976 subrouters := router.GetSubrouters() 977 suite.Len(subrouters, 2) 978 suite.NotSame(router.subrouters, subrouters) 979 } 980 981 func TestRouterTestSuite(t *testing.T) { 982 RunTest(t, new(RouterTestSuite)) 983 }