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  }