github.com/cloudwego/hertz@v0.9.3/pkg/route/routes_test.go (about)

     1  /*
     2   * Copyright 2022 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   * The MIT License (MIT)
    16   *
    17   * Copyright (c) 2014 Manuel Martínez-Almeida
    18   *
    19   * Permission is hereby granted, free of charge, to any person obtaining a copy
    20   * of this software and associated documentation files (the "Software"), to deal
    21   * in the Software without restriction, including without limitation the rights
    22   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    23   * copies of the Software, and to permit persons to whom the Software is
    24   * furnished to do so, subject to the following conditions:
    25   *
    26   * The above copyright notice and this permission notice shall be included in
    27   * all copies or substantial portions of the Software.
    28   *
    29   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    30   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    31   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    32   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    33   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    34   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    35   * THE SOFTWARE.
    36   *
    37   * This file may have been modified by CloudWeGo authors. All CloudWeGo
    38   * Modifications are Copyright 2022 CloudWeGo Authors
    39   */
    40  
    41  package route
    42  
    43  import (
    44  	"context"
    45  	"fmt"
    46  	"io/ioutil"
    47  	"net/http"
    48  	"net/http/httptest"
    49  	"os"
    50  	"path/filepath"
    51  	"strings"
    52  	"testing"
    53  
    54  	"github.com/cloudwego/hertz/pkg/app"
    55  	"github.com/cloudwego/hertz/pkg/common/config"
    56  	"github.com/cloudwego/hertz/pkg/common/test/assert"
    57  	"github.com/cloudwego/hertz/pkg/protocol"
    58  	"github.com/cloudwego/hertz/pkg/protocol/consts"
    59  )
    60  
    61  type header struct {
    62  	Key   string
    63  	Value string
    64  }
    65  
    66  func performRequest(e *Engine, method, path string, headers ...header) *httptest.ResponseRecorder {
    67  	ctx := e.ctxPool.Get().(*app.RequestContext)
    68  	ctx.HTMLRender = e.htmlRender
    69  
    70  	r := protocol.NewRequest(method, path, nil)
    71  	r.CopyTo(&ctx.Request)
    72  	for _, v := range headers {
    73  		ctx.Request.Header.Add(v.Key, v.Value)
    74  	}
    75  
    76  	e.ServeHTTP(context.Background(), ctx)
    77  
    78  	w := httptest.NewRecorder()
    79  	h := w.Header()
    80  	ctx.Response.Header.VisitAll(func(key, value []byte) {
    81  		h.Add(string(key), string(value))
    82  	})
    83  	w.WriteHeader(ctx.Response.StatusCode())
    84  	if _, err := w.Write(ctx.Response.Body()); err != nil {
    85  		panic(err.Error())
    86  	}
    87  	ctx.Reset()
    88  	e.ctxPool.Put(ctx)
    89  
    90  	return w
    91  }
    92  
    93  func testRouteOK(method string, t *testing.T) {
    94  	passed := false
    95  	passedAny := false
    96  	r := NewEngine(config.NewOptions(nil))
    97  	r.Any("/test2", func(c context.Context, ctx *app.RequestContext) {
    98  		passedAny = true
    99  	})
   100  	r.Handle(method, "/test", func(c context.Context, ctx *app.RequestContext) {
   101  		passed = true
   102  	})
   103  
   104  	w := performRequest(r, method, "/test")
   105  	assert.DeepEqual(t, true, passed)
   106  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   107  
   108  	performRequest(r, method, "/test2")
   109  	assert.DeepEqual(t, true, passedAny)
   110  }
   111  
   112  // TestSingleRouteOK tests that POST route is correctly invoked.
   113  func testRouteNotOK(method string, t *testing.T) {
   114  	passed := false
   115  	router := NewEngine(config.NewOptions(nil))
   116  	router.Handle(method, "/test_2", func(c context.Context, ctx *app.RequestContext) {
   117  		passed = true
   118  	})
   119  
   120  	w := performRequest(router, method, "/test")
   121  
   122  	assert.DeepEqual(t, false, passed)
   123  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   124  }
   125  
   126  // TestSingleRouteOK tests that POST route is correctly invoked.
   127  func testRouteNotOK2(method string, t *testing.T) {
   128  	passed := false
   129  	router := NewEngine(config.NewOptions(nil))
   130  	router.options.HandleMethodNotAllowed = true
   131  	var methodRoute string
   132  	if method == consts.MethodPost {
   133  		methodRoute = consts.MethodGet
   134  	} else {
   135  		methodRoute = consts.MethodPost
   136  	}
   137  	router.Handle(methodRoute, "/test", func(c context.Context, ctx *app.RequestContext) {
   138  		passed = true
   139  	})
   140  
   141  	w := performRequest(router, method, "/test")
   142  
   143  	assert.DeepEqual(t, false, passed)
   144  	assert.DeepEqual(t, consts.StatusMethodNotAllowed, w.Code)
   145  }
   146  
   147  func testRouteNotOK3(method string, t *testing.T) {
   148  	passed := false
   149  	router := NewEngine(config.NewOptions(nil))
   150  	router.Handle("GET", "/api/v:version/product/local/products/list", func(c context.Context, ctx *app.RequestContext) {
   151  		passed = true
   152  	})
   153  	router.Handle("GET", "/api/v:version/product/product_creation/preload_all_categories", func(c context.Context, ctx *app.RequestContext) {
   154  		passed = true
   155  	})
   156  
   157  	w := performRequest(router, method, "/api/v1/product/products/activate")
   158  
   159  	assert.DeepEqual(t, false, passed)
   160  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   161  }
   162  
   163  func TestRouterMethod(t *testing.T) {
   164  	router := NewEngine(config.NewOptions(nil))
   165  	router.PUT("/hey2", func(c context.Context, ctx *app.RequestContext) {
   166  		ctx.String(consts.StatusOK, "sup2")
   167  	})
   168  
   169  	router.PUT("/hey", func(c context.Context, ctx *app.RequestContext) {
   170  		ctx.String(consts.StatusOK, "called")
   171  	})
   172  
   173  	router.PUT("/hey3", func(c context.Context, ctx *app.RequestContext) {
   174  		ctx.String(consts.StatusOK, "sup3")
   175  	})
   176  
   177  	w := performRequest(router, consts.MethodPut, "/hey")
   178  
   179  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   180  	assert.DeepEqual(t, "called", w.Body.String())
   181  }
   182  
   183  func TestRouterGroupRouteOK(t *testing.T) {
   184  	testRouteOK(consts.MethodGet, t)
   185  	testRouteOK(consts.MethodPost, t)
   186  	testRouteOK(consts.MethodPut, t)
   187  	testRouteOK(consts.MethodPatch, t)
   188  	testRouteOK(consts.MethodHead, t)
   189  	testRouteOK(consts.MethodOptions, t)
   190  	testRouteOK(consts.MethodDelete, t)
   191  	testRouteOK(consts.MethodConnect, t)
   192  	testRouteOK(consts.MethodTrace, t)
   193  }
   194  
   195  func TestRouteNotOK1(t *testing.T) {
   196  	testRouteNotOK(consts.MethodGet, t)
   197  	testRouteNotOK(consts.MethodPost, t)
   198  	testRouteNotOK(consts.MethodPut, t)
   199  	testRouteNotOK(consts.MethodPatch, t)
   200  	testRouteNotOK(consts.MethodHead, t)
   201  	testRouteNotOK(consts.MethodOptions, t)
   202  	testRouteNotOK(consts.MethodDelete, t)
   203  	testRouteNotOK(consts.MethodConnect, t)
   204  	testRouteNotOK(consts.MethodTrace, t)
   205  }
   206  
   207  func TestRouteNotOK2(t *testing.T) {
   208  	testRouteNotOK2(consts.MethodGet, t)
   209  	testRouteNotOK2(consts.MethodPost, t)
   210  	testRouteNotOK2(consts.MethodPut, t)
   211  	testRouteNotOK2(consts.MethodPatch, t)
   212  	testRouteNotOK2(consts.MethodHead, t)
   213  	testRouteNotOK2(consts.MethodOptions, t)
   214  	testRouteNotOK2(consts.MethodDelete, t)
   215  	testRouteNotOK2(consts.MethodConnect, t)
   216  	testRouteNotOK2(consts.MethodTrace, t)
   217  }
   218  
   219  func TestRouteNotOK3(t *testing.T) {
   220  	testRouteNotOK3(consts.MethodGet, t)
   221  	testRouteNotOK3(consts.MethodPost, t)
   222  	testRouteNotOK3(consts.MethodPut, t)
   223  	testRouteNotOK3(consts.MethodPatch, t)
   224  	testRouteNotOK3(consts.MethodHead, t)
   225  	testRouteNotOK3(consts.MethodOptions, t)
   226  	testRouteNotOK3(consts.MethodDelete, t)
   227  	testRouteNotOK3(consts.MethodConnect, t)
   228  	testRouteNotOK3(consts.MethodTrace, t)
   229  }
   230  
   231  func TestRouteRedirectTrailingSlash(t *testing.T) {
   232  	router := NewEngine(config.NewOptions(nil))
   233  	router.options.RedirectFixedPath = false
   234  	router.options.RedirectTrailingSlash = true
   235  	router.GET("/path", func(c context.Context, ctx *app.RequestContext) {})
   236  	router.GET("/path2/", func(c context.Context, ctx *app.RequestContext) {})
   237  	router.POST("/path3", func(c context.Context, ctx *app.RequestContext) {})
   238  	router.PUT("/path4/", func(c context.Context, ctx *app.RequestContext) {})
   239  
   240  	w := performRequest(router, consts.MethodGet, "/path/")
   241  	assert.DeepEqual(t, "/path", w.Header().Get("Location"))
   242  	assert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)
   243  
   244  	w = performRequest(router, consts.MethodGet, "/path2")
   245  	assert.DeepEqual(t, "/path2/", w.Header().Get("Location"))
   246  	assert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)
   247  
   248  	w = performRequest(router, consts.MethodPost, "/path3/")
   249  	assert.DeepEqual(t, "/path3", w.Header().Get("Location"))
   250  	assert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)
   251  
   252  	w = performRequest(router, consts.MethodPut, "/path4")
   253  	assert.DeepEqual(t, "/path4/", w.Header().Get("Location"))
   254  	assert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)
   255  
   256  	w = performRequest(router, consts.MethodGet, "/path")
   257  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   258  
   259  	w = performRequest(router, consts.MethodGet, "/path2/")
   260  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   261  
   262  	w = performRequest(router, consts.MethodPost, "/path3")
   263  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   264  
   265  	w = performRequest(router, consts.MethodPut, "/path4/")
   266  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   267  
   268  	w = performRequest(router, consts.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
   269  	assert.DeepEqual(t, "/api/path2/", w.Header().Get("Location"))
   270  	assert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)
   271  
   272  	w = performRequest(router, consts.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
   273  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   274  
   275  	router.options.RedirectTrailingSlash = false
   276  
   277  	w = performRequest(router, consts.MethodGet, "/path/")
   278  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   279  	w = performRequest(router, consts.MethodGet, "/path2")
   280  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   281  	w = performRequest(router, consts.MethodPost, "/path3/")
   282  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   283  	w = performRequest(router, consts.MethodPut, "/path4")
   284  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   285  }
   286  
   287  func TestRouteRedirectTrailingSlashWithQuery(t *testing.T) {
   288  	router := NewEngine(config.NewOptions(nil))
   289  	router.options.RedirectFixedPath = false
   290  	router.options.RedirectTrailingSlash = true
   291  	router.GET("/path", func(c context.Context, ctx *app.RequestContext) {})
   292  	router.GET("/path2/", func(c context.Context, ctx *app.RequestContext) {})
   293  
   294  	w := performRequest(router, consts.MethodGet, "/path/?offset=2")
   295  	assert.DeepEqual(t, "/path?offset=2", w.Header().Get("Location"))
   296  	assert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)
   297  
   298  	w = performRequest(router, consts.MethodGet, "/path2?offset=2")
   299  	assert.DeepEqual(t, "/path2/?offset=2", w.Header().Get("Location"))
   300  	assert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)
   301  }
   302  
   303  func TestRouteRedirectFixedPath(t *testing.T) {
   304  	router := NewEngine(config.NewOptions(nil))
   305  	router.options.RedirectFixedPath = true
   306  	router.options.RedirectTrailingSlash = false
   307  
   308  	router.GET("/path", func(c context.Context, ctx *app.RequestContext) {})
   309  	router.GET("/Path2", func(c context.Context, ctx *app.RequestContext) {})
   310  	router.POST("/PATH3", func(c context.Context, ctx *app.RequestContext) {})
   311  	router.POST("/Path4/", func(c context.Context, ctx *app.RequestContext) {})
   312  
   313  	w := performRequest(router, consts.MethodGet, "/PATH")
   314  	assert.DeepEqual(t, "/path", w.Header().Get("Location"))
   315  	assert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)
   316  
   317  	w = performRequest(router, consts.MethodGet, "/path2")
   318  	assert.DeepEqual(t, "/Path2", w.Header().Get("Location"))
   319  	assert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)
   320  
   321  	w = performRequest(router, consts.MethodPost, "/path3")
   322  	assert.DeepEqual(t, "/PATH3", w.Header().Get("Location"))
   323  	assert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)
   324  
   325  	w = performRequest(router, consts.MethodPost, "/path4")
   326  	assert.DeepEqual(t, "/Path4/", w.Header().Get("Location"))
   327  	assert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)
   328  }
   329  
   330  // TestContextParamsGet tests that a parameter can be parsed from the URL.
   331  func TestRouteParamsByName(t *testing.T) {
   332  	name := ""
   333  	lastName := ""
   334  	wild := ""
   335  	router := NewEngine(config.NewOptions(nil))
   336  	router.GET("/test/:name/:last_name/*wild", func(c context.Context, ctx *app.RequestContext) {
   337  		name = ctx.Params.ByName("name")
   338  		lastName = ctx.Params.ByName("last_name")
   339  		var ok bool
   340  		wild, ok = ctx.Params.Get("wild")
   341  
   342  		assert.DeepEqual(t, true, ok)
   343  		assert.DeepEqual(t, name, ctx.Param("name"))
   344  		assert.DeepEqual(t, name, ctx.Param("name"))
   345  		assert.DeepEqual(t, lastName, ctx.Param("last_name"))
   346  
   347  		assert.DeepEqual(t, "", ctx.Param("wtf"))
   348  		assert.DeepEqual(t, "", ctx.Params.ByName("wtf"))
   349  
   350  		wtf, ok := ctx.Params.Get("wtf")
   351  		assert.DeepEqual(t, "", wtf)
   352  		assert.False(t, ok)
   353  	})
   354  
   355  	w := performRequest(router, consts.MethodGet, "/test/john/smith/is/super/great")
   356  
   357  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   358  	assert.DeepEqual(t, "john", name)
   359  	assert.DeepEqual(t, "smith", lastName)
   360  	assert.DeepEqual(t, "is/super/great", wild)
   361  }
   362  
   363  // TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes.
   364  func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
   365  	name := ""
   366  	lastName := ""
   367  	wild := ""
   368  	router := NewEngine(config.NewOptions(nil))
   369  	router.options.RemoveExtraSlash = true
   370  	router.GET("/test/:name/:last_name/*wild", func(c context.Context, ctx *app.RequestContext) {
   371  		name = ctx.Params.ByName("name")
   372  		lastName = ctx.Params.ByName("last_name")
   373  		var ok bool
   374  		wild, ok = ctx.Params.Get("wild")
   375  
   376  		assert.True(t, ok)
   377  		assert.DeepEqual(t, name, ctx.Param("name"))
   378  		assert.DeepEqual(t, name, ctx.Param("name"))
   379  		assert.DeepEqual(t, lastName, ctx.Param("last_name"))
   380  
   381  		assert.DeepEqual(t, "", ctx.Param("wtf"))
   382  		assert.DeepEqual(t, "", ctx.Params.ByName("wtf"))
   383  
   384  		wtf, ok := ctx.Params.Get("wtf")
   385  		assert.DeepEqual(t, "", wtf)
   386  		assert.False(t, ok)
   387  	})
   388  
   389  	w := performRequest(router, consts.MethodGet, "/test//john//smith//is//super//great")
   390  
   391  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   392  	assert.DeepEqual(t, "john", name)
   393  	assert.DeepEqual(t, "smith", lastName)
   394  	assert.DeepEqual(t, "is/super/great", wild)
   395  }
   396  
   397  // TestHandleStaticFile - ensure the static file handles properly
   398  func TestRouteStaticFile(t *testing.T) {
   399  	// SETUP file
   400  	testRoot, _ := os.Getwd()
   401  	f, err := ioutil.TempFile(testRoot, "")
   402  	if err != nil {
   403  		t.Error(err)
   404  	}
   405  	defer os.Remove(f.Name())
   406  	_, err = f.WriteString("Hertz Web Framework")
   407  	assert.Nil(t, err)
   408  	f.Close()
   409  
   410  	dir, filename := filepath.Split(f.Name())
   411  
   412  	// SETUP engine
   413  	router := NewEngine(config.NewOptions(nil))
   414  	router.StaticFS("/using_static", &app.FS{Root: dir, AcceptByteRange: true, PathRewrite: app.NewPathSlashesStripper(1)})
   415  	router.StaticFile("/result", f.Name())
   416  
   417  	w := performRequest(router, consts.MethodGet, "/using_static/"+filename)
   418  	w2 := performRequest(router, consts.MethodGet, "/result")
   419  
   420  	assert.DeepEqual(t, w, w2)
   421  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   422  	assert.DeepEqual(t, "Hertz Web Framework", w.Body.String())
   423  	assert.DeepEqual(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
   424  
   425  	w3 := performRequest(router, consts.MethodHead, "/using_static/"+filename)
   426  	w4 := performRequest(router, consts.MethodHead, "/result")
   427  
   428  	assert.DeepEqual(t, w3, w4)
   429  	assert.DeepEqual(t, consts.StatusOK, w3.Code)
   430  }
   431  
   432  // TestHandleStaticDir - ensure the root/sub dir handles properly
   433  func TestRouteStaticListingDir(t *testing.T) {
   434  	router := NewEngine(config.NewOptions(nil))
   435  	router.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})
   436  
   437  	w := performRequest(router, consts.MethodGet, "/")
   438  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   439  
   440  	assert.True(t, strings.Contains(w.Body.String(), "engine.go"))
   441  	assert.DeepEqual(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
   442  }
   443  
   444  // TestHandleHeadToDir - ensure the root/sub dir handles properly
   445  func TestRouteStaticNoListing(t *testing.T) {
   446  	router := NewEngine(config.NewOptions(nil))
   447  	router.Static("/", "./")
   448  
   449  	w := performRequest(router, consts.MethodGet, "/")
   450  
   451  	assert.DeepEqual(t, http.StatusForbidden, w.Code)
   452  	assert.False(t, strings.Contains(w.Body.String(), "engine.go"))
   453  }
   454  
   455  func TestRouterMiddlewareAndStatic(t *testing.T) {
   456  	router := NewEngine(config.NewOptions(nil))
   457  	static := router.Group("/", func(c context.Context, ctx *app.RequestContext) {
   458  		ctx.Response.Header.Set("Last-Modified", "Mon, 02 Jan 2006 15:04:05 MST")
   459  		ctx.Response.Header.Set("Last-Modified", "Mon, 02 Jan 2006 15:04:05 MST")
   460  		ctx.Response.Header.Set("Expires", "Mon, 02 Jan 2006 15:04:05 MST")
   461  		ctx.Response.Header.Set("X-Hertz", "Hertz Framework")
   462  	})
   463  	static.Static("/", "./")
   464  
   465  	w := performRequest(router, consts.MethodGet, "/engine.go")
   466  
   467  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   468  	assert.True(t, strings.Contains(w.Body.String(), "package route"))
   469  	// when Go version <= 1.16, mime.TypeByExtension will return Content-Type='text/plain; charset=utf-8',
   470  	// otherwise it will return Content-Type='text/x-go; charset=utf-8'
   471  	assert.NotEqual(t, "", w.Header().Get("Content-Type"))
   472  	assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
   473  	assert.DeepEqual(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
   474  	assert.DeepEqual(t, "Hertz Framework", w.Header().Get("x-Hertz"))
   475  }
   476  
   477  func TestRouteNotAllowedEnabled(t *testing.T) {
   478  	router := NewEngine(config.NewOptions(nil))
   479  	router.options.HandleMethodNotAllowed = true
   480  	router.POST("/path", func(c context.Context, ctx *app.RequestContext) {})
   481  	w := performRequest(router, consts.MethodGet, "/path")
   482  	assert.DeepEqual(t, consts.StatusMethodNotAllowed, w.Code)
   483  
   484  	router.NoMethod(func(c context.Context, ctx *app.RequestContext) {
   485  		ctx.String(http.StatusTeapot, "responseText")
   486  	})
   487  	w = performRequest(router, consts.MethodGet, "/path")
   488  	assert.DeepEqual(t, "responseText", w.Body.String())
   489  	assert.DeepEqual(t, http.StatusTeapot, w.Code)
   490  }
   491  
   492  func TestRouteNotAllowedEnabled2(t *testing.T) {
   493  	router := NewEngine(config.NewOptions(nil))
   494  	router.options.HandleMethodNotAllowed = true
   495  	// add one methodTree to trees
   496  	router.addRoute(consts.MethodPost, "/", app.HandlersChain{func(_ context.Context, _ *app.RequestContext) {}})
   497  	router.GET("/path2", func(c context.Context, ctx *app.RequestContext) {})
   498  	w := performRequest(router, consts.MethodPost, "/path2")
   499  	assert.DeepEqual(t, consts.StatusMethodNotAllowed, w.Code)
   500  }
   501  
   502  func TestRouteNotAllowedDisabled(t *testing.T) {
   503  	router := NewEngine(config.NewOptions(nil))
   504  	router.options.HandleMethodNotAllowed = false
   505  	router.POST("/path", func(c context.Context, ctx *app.RequestContext) {})
   506  	w := performRequest(router, consts.MethodGet, "/path")
   507  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   508  
   509  	router.NoMethod(func(c context.Context, ctx *app.RequestContext) {
   510  		ctx.String(http.StatusTeapot, "responseText")
   511  	})
   512  	w = performRequest(router, consts.MethodGet, "/path")
   513  	assert.DeepEqual(t, "404 page not found", w.Body.String())
   514  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   515  }
   516  
   517  func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
   518  	router := NewEngine(config.NewOptions(nil))
   519  	router.options.RemoveExtraSlash = true
   520  	router.GET("/path", func(c context.Context, ctx *app.RequestContext) {})
   521  	router.GET("/", func(c context.Context, ctx *app.RequestContext) {})
   522  
   523  	testRoutes := []struct {
   524  		route    string
   525  		code     int
   526  		location string
   527  	}{
   528  		{"/../path", consts.StatusOK, ""},    // CleanPath
   529  		{"/nope", consts.StatusNotFound, ""}, // NotFound
   530  	}
   531  	for _, tr := range testRoutes {
   532  		w := performRequest(router, "GET", tr.route)
   533  		assert.DeepEqual(t, tr.code, w.Code)
   534  		if w.Code != consts.StatusNotFound {
   535  			assert.DeepEqual(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
   536  		}
   537  	}
   538  }
   539  
   540  func TestRouterNotFound(t *testing.T) {
   541  	router := NewEngine(config.NewOptions(nil))
   542  	router.options.RedirectFixedPath = true
   543  	router.GET("/path", func(c context.Context, ctx *app.RequestContext) {})
   544  	router.GET("/dir/", func(c context.Context, ctx *app.RequestContext) {})
   545  	router.GET("/", func(c context.Context, ctx *app.RequestContext) {})
   546  
   547  	testRoutes := []struct {
   548  		route    string
   549  		code     int
   550  		location string
   551  	}{
   552  		{"/path/", consts.StatusMovedPermanently, "/path"},    // TSR -/
   553  		{"/dir", consts.StatusMovedPermanently, "/dir/"},      // TSR +/
   554  		{"/PATH", consts.StatusMovedPermanently, "/path"},     // Fixed Case
   555  		{"/DIR/", consts.StatusMovedPermanently, "/dir/"},     // Fixed Case
   556  		{"/PATH/", consts.StatusMovedPermanently, "/path"},    // Fixed Case -/
   557  		{"/DIR", consts.StatusMovedPermanently, "/dir/"},      // Fixed Case +/
   558  		{"/../path/", consts.StatusMovedPermanently, "/path"}, // Without CleanPath
   559  		{"/nope", consts.StatusNotFound, ""},                  // NotFound
   560  	}
   561  	for _, tr := range testRoutes {
   562  		w := performRequest(router, consts.MethodGet, tr.route)
   563  		assert.DeepEqual(t, tr.code, w.Code)
   564  		if w.Code != consts.StatusNotFound {
   565  			assert.DeepEqual(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
   566  		}
   567  	}
   568  
   569  	// Test custom not found handler
   570  	var notFound bool
   571  	router.NoRoute(func(c context.Context, ctx *app.RequestContext) {
   572  		ctx.AbortWithStatus(consts.StatusNotFound)
   573  		notFound = true
   574  	})
   575  	w := performRequest(router, consts.MethodGet, "/nope")
   576  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   577  	assert.True(t, notFound)
   578  
   579  	// Test other method than GET (want 307 instead of 301)
   580  	router.PATCH("/path", func(c context.Context, ctx *app.RequestContext) {})
   581  	w = performRequest(router, consts.MethodPatch, "/path/")
   582  	assert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)
   583  	assert.DeepEqual(t, "map[Content-Type:[text/plain; charset=utf-8] Location:[/path]]", fmt.Sprint(w.Header()))
   584  
   585  	// Test special case where no node for the prefix "/" exists
   586  	router = NewEngine(config.NewOptions(nil))
   587  	router.GET("/a", func(c context.Context, ctx *app.RequestContext) {})
   588  	w = performRequest(router, consts.MethodGet, "/")
   589  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   590  }
   591  
   592  func TestRouterStaticFSNotFound(t *testing.T) {
   593  	router := NewEngine(config.NewOptions(nil))
   594  	router.StaticFS("/", &app.FS{Root: "/thisreallydoesntexist/"})
   595  	router.NoRoute(func(c context.Context, ctx *app.RequestContext) {
   596  		ctx.String(consts.StatusNotFound, "non existent")
   597  	})
   598  
   599  	w := performRequest(router, consts.MethodGet, "/nonexistent")
   600  	assert.DeepEqual(t, "Cannot open requested path", w.Body.String())
   601  
   602  	w = performRequest(router, consts.MethodHead, "/nonexistent")
   603  	assert.DeepEqual(t, "Cannot open requested path", w.Body.String())
   604  }
   605  
   606  func TestRouterStaticFSFileNotFound(t *testing.T) {
   607  	router := NewEngine(config.NewOptions(nil))
   608  
   609  	router.StaticFS("/", &app.FS{Root: "."})
   610  
   611  	assert.NotPanic(t, func() {
   612  		performRequest(router, consts.MethodGet, "/nonexistent")
   613  	})
   614  }
   615  
   616  func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
   617  	router := NewEngine(config.NewOptions(nil))
   618  
   619  	// Middleware must be called just only once by per request.
   620  	middlewareCalledNum := 0
   621  	router.Use(func(c context.Context, ctx *app.RequestContext) {
   622  		middlewareCalledNum++
   623  	})
   624  
   625  	router.StaticFS("/", &app.FS{Root: "/thisreallydoesntexist/"})
   626  
   627  	// First access
   628  	performRequest(router, consts.MethodGet, "/nonexistent")
   629  	assert.DeepEqual(t, 1, middlewareCalledNum)
   630  
   631  	// Second access
   632  	performRequest(router, consts.MethodHead, "/nonexistent")
   633  	assert.DeepEqual(t, 2, middlewareCalledNum)
   634  }
   635  
   636  func TestRouteRawPath(t *testing.T) {
   637  	route := NewEngine(config.NewOptions(nil))
   638  	route.options.UseRawPath = true
   639  
   640  	route.POST("/project/:name/build/:num", func(c context.Context, ctx *app.RequestContext) {
   641  		name := ctx.Params.ByName("name")
   642  		num := ctx.Params.ByName("num")
   643  
   644  		assert.DeepEqual(t, name, ctx.Param("name"))
   645  		assert.DeepEqual(t, num, ctx.Param("num"))
   646  
   647  		assert.DeepEqual(t, "Some/Other/Project", name)
   648  		assert.DeepEqual(t, "222", num)
   649  	})
   650  
   651  	w := performRequest(route, consts.MethodPost, "/project/Some%2FOther%2FProject/build/222")
   652  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   653  }
   654  
   655  func TestRouteRawPathNoUnescape(t *testing.T) {
   656  	route := NewEngine(config.NewOptions(nil))
   657  	route.options.UseRawPath = true
   658  	route.options.UnescapePathValues = false
   659  
   660  	route.POST("/project/:name/build/:num", func(c context.Context, ctx *app.RequestContext) {
   661  		name := ctx.Params.ByName("name")
   662  		num := ctx.Params.ByName("num")
   663  
   664  		assert.DeepEqual(t, name, ctx.Param("name"))
   665  		assert.DeepEqual(t, num, ctx.Param("num"))
   666  
   667  		assert.DeepEqual(t, "Some%2FOther%2FProject", name)
   668  		assert.DeepEqual(t, "333", num)
   669  	})
   670  
   671  	w := performRequest(route, consts.MethodPost, "/project/Some%2FOther%2FProject/build/333")
   672  	assert.DeepEqual(t, consts.StatusOK, w.Code)
   673  }
   674  
   675  func TestRouteServeErrorWithWriteHeader(t *testing.T) {
   676  	route := NewEngine(config.NewOptions(nil))
   677  	route.Use(func(c context.Context, ctx *app.RequestContext) {
   678  		ctx.SetStatusCode(421)
   679  		ctx.Next(c)
   680  	})
   681  
   682  	w := performRequest(route, consts.MethodGet, "/NotFound")
   683  	assert.DeepEqual(t, 421, w.Code)
   684  	assert.DeepEqual(t, 0, w.Body.Len())
   685  }
   686  
   687  func TestRouteContextHoldsFullPath(t *testing.T) {
   688  	router := NewEngine(config.NewOptions(nil))
   689  
   690  	// Test routes
   691  	routes := []string{
   692  		"/simple",
   693  		"/project/:name",
   694  		"/",
   695  		"/news/home",
   696  		"/news",
   697  		"/simple-two/one",
   698  		"/simple-two/one-two",
   699  		"/project/:name/build/*params",
   700  		"/project/:name/bui",
   701  		"/user/:id/status",
   702  		"/user/:id",
   703  		"/user/:id/profile",
   704  	}
   705  
   706  	for _, route := range routes {
   707  		actualRoute := route
   708  		router.GET(route, func(c context.Context, ctx *app.RequestContext) {
   709  			// For each defined route context should contain its full path
   710  			assert.DeepEqual(t, actualRoute, ctx.FullPath())
   711  			ctx.AbortWithStatus(consts.StatusOK)
   712  		})
   713  	}
   714  
   715  	for _, route := range routes {
   716  		w := performRequest(router, consts.MethodGet, route)
   717  		assert.DeepEqual(t, consts.StatusOK, w.Code)
   718  	}
   719  
   720  	// Test not found
   721  	router.Use(func(c context.Context, ctx *app.RequestContext) {
   722  		// For not found routes full path is empty
   723  		assert.DeepEqual(t, "", ctx.FullPath())
   724  	})
   725  
   726  	w := performRequest(router, consts.MethodGet, "/not-found")
   727  	assert.DeepEqual(t, consts.StatusNotFound, w.Code)
   728  }
   729  
   730  func checkUnusedParamValues(t *testing.T, ctx *app.RequestContext, expectParam map[string]string) {
   731  	for _, p := range ctx.Params {
   732  		if expectParam == nil {
   733  			t.Errorf("pValue '%+v' is set for param name '%v' but we are not expecting it", p.Value, p.Key)
   734  		} else if val, ok := expectParam[p.Key]; !ok || val != p.Value {
   735  			t.Errorf("'%+v' is set for param name '%v' but we are expecting it with expectParam '%+v'", p.Value, p.Key, val)
   736  		}
   737  	}
   738  }
   739  
   740  var handlerFunc = func(route string) app.HandlersChain {
   741  	return app.HandlersChain{func(c context.Context, ctx *app.RequestContext) {
   742  		ctx.Set("path", route)
   743  	}}
   744  }
   745  
   746  var handlerHelper = func(route, key string, value int) app.HandlersChain {
   747  	return app.HandlersChain{func(c context.Context, ctx *app.RequestContext) {
   748  		ctx.Set(key, value)
   749  		ctx.Set("path", route)
   750  	}}
   751  }
   752  
   753  func getHelper(c *app.RequestContext, key string) interface{} {
   754  	p, _ := c.Get(key)
   755  	return p
   756  }
   757  
   758  func TestRouterStatic(t *testing.T) {
   759  	e := NewEngine(config.NewOptions(nil))
   760  	path := "/folders/a/files/hertz.gif"
   761  	e.addRoute(consts.MethodGet, path, handlerFunc(path))
   762  	c := e.NewContext()
   763  	c.Request.SetRequestURI(path)
   764  	c.Request.Header.SetMethod(consts.MethodGet)
   765  	e.ServeHTTP(context.Background(), c)
   766  	assert.DeepEqual(t, path, getHelper(c, "path"))
   767  }
   768  
   769  func TestRouterParam(t *testing.T) {
   770  	e := NewEngine(config.NewOptions(nil))
   771  	e.addRoute(consts.MethodGet, "/users/:id", handlerFunc("/users/:id"))
   772  
   773  	testCases := []struct {
   774  		name        string
   775  		whenURL     string
   776  		expectRoute interface{}
   777  		expectParam map[string]string
   778  	}{
   779  		{
   780  			name:        "route /users/1 to /users/:id",
   781  			whenURL:     "/users/1",
   782  			expectRoute: "/users/:id",
   783  			expectParam: map[string]string{"id": "1"},
   784  		},
   785  		{
   786  			name:        "route /users/1/ to /users/:id",
   787  			whenURL:     "/users/1/",
   788  			expectRoute: nil,
   789  			expectParam: nil,
   790  		},
   791  	}
   792  
   793  	for _, tc := range testCases {
   794  		t.Run(tc.name, func(t *testing.T) {
   795  			c := e.NewContext()
   796  			c.Request.SetRequestURI(tc.whenURL)
   797  			c.Request.Header.SetMethod(consts.MethodGet)
   798  			e.ServeHTTP(context.Background(), c)
   799  			assert.DeepEqual(t, tc.expectRoute, getHelper(c, "path"))
   800  			checkUnusedParamValues(t, c, tc.expectParam)
   801  		})
   802  	}
   803  }
   804  
   805  func TestRouterTwoParam(t *testing.T) {
   806  	e := NewEngine(config.NewOptions(nil))
   807  	e.addRoute(consts.MethodGet, "/users/:uid/files/:fid", handlerFunc("/users/:uid/files/:fid"))
   808  	ctx := e.NewContext()
   809  	ctx.Request.SetRequestURI("/users/1/files/1")
   810  	ctx.Request.Header.SetMethod(consts.MethodGet)
   811  	e.ServeHTTP(context.Background(), ctx)
   812  
   813  	assert.DeepEqual(t, "1", ctx.Param("uid"))
   814  	assert.DeepEqual(t, "1", ctx.Param("fid"))
   815  }
   816  
   817  func TestRouterParamWithSlash(t *testing.T) {
   818  	e := NewEngine(config.NewOptions(nil))
   819  
   820  	e.addRoute(consts.MethodGet, "/a/:b/c/d/:e", handlerFunc("/a/:b/c/d/:e"))
   821  	e.addRoute(consts.MethodGet, "/a/:b/c/:d/:f", handlerFunc("/a/:b/c/:d/:f"))
   822  
   823  	ctx := e.NewContext()
   824  	ctx.Request.SetRequestURI("/a/1/c/d/2/3")
   825  	ctx.Request.Header.SetMethod(consts.MethodGet)
   826  	e.ServeHTTP(context.Background(), ctx)
   827  	assert.Nil(t, getHelper(ctx, "path"))
   828  	assert.DeepEqual(t, consts.StatusNotFound, ctx.Response.StatusCode())
   829  }
   830  
   831  func TestRouteMultiLevelBacktracking(t *testing.T) {
   832  	testCases := []struct {
   833  		name        string
   834  		whenURL     string
   835  		expectRoute interface{}
   836  		expectParam map[string]string
   837  	}{
   838  		{
   839  			name:        "route /a/c/df to /a/c/df",
   840  			whenURL:     "/a/c/df",
   841  			expectRoute: "/a/c/df",
   842  		},
   843  		{
   844  			name:        "route /a/x/df to /a/:b/c",
   845  			whenURL:     "/a/x/c",
   846  			expectRoute: "/a/:b/c",
   847  			expectParam: map[string]string{"b": "x"},
   848  		},
   849  		// {
   850  		// 	name:        "route /a/x/f to /a/*/f",
   851  		// 	whenURL:     "/a/x/f",
   852  		// 	expectRoute: "/a/*/f",
   853  		// 	expectParam: map[string]string{"x": "x/f"}, // NOTE: `x` would be probably more suitable
   854  		// },
   855  		{
   856  			name:        "route /b/c/f to /:e/c/f",
   857  			whenURL:     "/b/c/f",
   858  			expectRoute: "/:e/c/f",
   859  			expectParam: map[string]string{"e": "b"},
   860  		},
   861  		{
   862  			name:        "route /b/c/c to /*x",
   863  			whenURL:     "/b/c/c",
   864  			expectRoute: "/*x",
   865  			expectParam: map[string]string{"x": "b/c/c"},
   866  		},
   867  	}
   868  
   869  	e := NewEngine(config.NewOptions(nil))
   870  
   871  	e.addRoute(consts.MethodGet, "/a/:b/c", handlerHelper("/a/:b/c", "case", 1))
   872  	e.addRoute(consts.MethodGet, "/a/c/d", handlerHelper("/a/c/d", "case", 2))
   873  	e.addRoute(consts.MethodGet, "/a/c/df", handlerHelper("/a/c/df", "case", 3))
   874  	// e.addRoute(consts.MethodGet, "/a/*/f", handlerHelper("case", 4))
   875  	e.addRoute(consts.MethodGet, "/:e/c/f", handlerHelper("/:e/c/f", "case", 5))
   876  	e.addRoute(consts.MethodGet, "/*x", handlerHelper("/*x", "case", 6))
   877  
   878  	for _, tc := range testCases {
   879  		t.Run(tc.name, func(t *testing.T) {
   880  			ctx := e.NewContext()
   881  			ctx.Request.SetRequestURI(tc.whenURL)
   882  			ctx.Request.Header.SetMethod(consts.MethodGet)
   883  			e.ServeHTTP(context.Background(), ctx)
   884  
   885  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
   886  			for param, expectedValue := range tc.expectParam {
   887  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
   888  			}
   889  			checkUnusedParamValues(t, ctx, tc.expectParam)
   890  		})
   891  	}
   892  }
   893  
   894  func TestRouteMultiLevelBacktracking2(t *testing.T) {
   895  	e := NewEngine(config.NewOptions(nil))
   896  	e.addRoute(consts.MethodGet, "/a/:b/c", handlerFunc("/a/:b/c"))
   897  	e.addRoute(consts.MethodGet, "/a/c/d", handlerFunc("/a/c/d"))
   898  	e.addRoute(consts.MethodGet, "/a/c/df", handlerFunc("/a/c/df"))
   899  	e.addRoute(consts.MethodGet, "/:e/c/f", handlerFunc("/:e/c/f"))
   900  	e.addRoute(consts.MethodGet, "/*x", handlerFunc("/*x"))
   901  
   902  	testCases := []struct {
   903  		name        string
   904  		whenURL     string
   905  		expectRoute string
   906  		expectParam map[string]string
   907  	}{
   908  		{
   909  			name:        "route /a/c/df to /a/c/df",
   910  			whenURL:     "/a/c/df",
   911  			expectRoute: "/a/c/df",
   912  		},
   913  		{
   914  			name:        "route /a/x/df to /a/:b/c",
   915  			whenURL:     "/a/x/c",
   916  			expectRoute: "/a/:b/c",
   917  			expectParam: map[string]string{"b": "x"},
   918  		},
   919  		{
   920  			name:        "route /a/c/f to /:e/c/f",
   921  			whenURL:     "/a/c/f",
   922  			expectRoute: "/:e/c/f",
   923  			expectParam: map[string]string{"e": "a"},
   924  		},
   925  		{
   926  			name:        "route /b/c/f to /:e/c/f",
   927  			whenURL:     "/b/c/f",
   928  			expectRoute: "/:e/c/f",
   929  			expectParam: map[string]string{"e": "b"},
   930  		},
   931  		{
   932  			name:        "route /b/c/c to /*",
   933  			whenURL:     "/b/c/c",
   934  			expectRoute: "/*x",
   935  			expectParam: map[string]string{"x": "b/c/c"},
   936  		},
   937  		{ // this traverses `/a/:b/c` and `/:e/c/f` branches and eventually backtracks to `/*`
   938  			name:        "route /a/c/cf to /*",
   939  			whenURL:     "/a/c/cf",
   940  			expectRoute: "/*x",
   941  			expectParam: map[string]string{"x": "a/c/cf"},
   942  		},
   943  		{
   944  			name:        "route /anyMatch to /*",
   945  			whenURL:     "/anyMatch",
   946  			expectRoute: "/*x",
   947  			expectParam: map[string]string{"x": "anyMatch"},
   948  		},
   949  		{
   950  			name:        "route /anyMatch/withSlash to /*",
   951  			whenURL:     "/anyMatch/withSlash",
   952  			expectRoute: "/*x",
   953  			expectParam: map[string]string{"x": "anyMatch/withSlash"},
   954  		},
   955  	}
   956  
   957  	for _, tc := range testCases {
   958  		t.Run(tc.name, func(t *testing.T) {
   959  			ctx := e.NewContext()
   960  			ctx.Request.SetRequestURI(tc.whenURL)
   961  			ctx.Request.Header.SetMethod(consts.MethodGet)
   962  			e.ServeHTTP(context.Background(), ctx)
   963  
   964  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
   965  			for param, expectedValue := range tc.expectParam {
   966  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
   967  			}
   968  			checkUnusedParamValues(t, ctx, tc.expectParam)
   969  		})
   970  	}
   971  }
   972  
   973  func TestRouterBacktrackingFromMultipleParamKinds(t *testing.T) {
   974  	e := NewEngine(config.NewOptions(nil))
   975  	e.addRoute(consts.MethodGet, "/*x", handlerFunc("/*x")) // this can match only path that does not have slash in it
   976  	e.addRoute(consts.MethodGet, "/:1/second", handlerFunc("/:1/second"))
   977  	e.addRoute(consts.MethodGet, "/:1/:2", handlerFunc("/:1/:2")) // this acts as match ANY for all routes that have at least one slash
   978  	e.addRoute(consts.MethodGet, "/:1/:2/third", handlerFunc("/:1/:2/third"))
   979  	e.addRoute(consts.MethodGet, "/:1/:2/:3/fourth", handlerFunc("/:1/:2/:3/fourth"))
   980  	e.addRoute(consts.MethodGet, "/:1/:2/:3/:4/fifth", handlerFunc("/:1/:2/:3/:4/fifth"))
   981  
   982  	testCases := []struct {
   983  		name        string
   984  		whenURL     string
   985  		expectRoute string
   986  		expectParam map[string]string
   987  	}{
   988  		{
   989  			name:        "route /first to /*",
   990  			whenURL:     "/first",
   991  			expectRoute: "/*x",
   992  			expectParam: map[string]string{"x": "first"},
   993  		},
   994  		{
   995  			name:        "route /first/second to /:1/second",
   996  			whenURL:     "/first/second",
   997  			expectRoute: "/:1/second",
   998  			expectParam: map[string]string{"1": "first"},
   999  		},
  1000  		{
  1001  			name:        "route /first/second-new to /:1/:2",
  1002  			whenURL:     "/first/second-new",
  1003  			expectRoute: "/:1/:2",
  1004  			expectParam: map[string]string{
  1005  				"1": "first",
  1006  				"2": "second-new",
  1007  			},
  1008  		},
  1009  		{
  1010  			name:        "route /first/second/ to /:1/:2",
  1011  			whenURL:     "/first/second/",
  1012  			expectRoute: "/*x", // "/:1/:2",
  1013  			expectParam: map[string]string{"x": "first/second/"},
  1014  		},
  1015  		{
  1016  			name:        "route /first/second/third/fourth/fifth/nope to /:1/:2",
  1017  			whenURL:     "/first/second/third/fourth/fifth/nope",
  1018  			expectRoute: "/*x",
  1019  			expectParam: map[string]string{"x": "first/second/third/fourth/fifth/nope"},
  1020  		},
  1021  	}
  1022  
  1023  	for _, tc := range testCases {
  1024  		t.Run(tc.name, func(t *testing.T) {
  1025  			ctx := e.NewContext()
  1026  			ctx.Request.SetRequestURI(tc.whenURL)
  1027  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1028  			e.ServeHTTP(context.Background(), ctx)
  1029  
  1030  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1031  			for param, expectedValue := range tc.expectParam {
  1032  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1033  			}
  1034  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1035  		})
  1036  	}
  1037  }
  1038  
  1039  func TestRouterParamStaticConflict(t *testing.T) {
  1040  	e := NewEngine(config.NewOptions(nil))
  1041  
  1042  	g := e.Group("/g")
  1043  	g.GET("/skills", handlerFunc("/g/skills")...)
  1044  	g.GET("/status", handlerFunc("/g/status")...)
  1045  	g.GET("/:name", handlerFunc("/g/:name")...)
  1046  
  1047  	testCases := []struct {
  1048  		whenURL     string
  1049  		expectRoute interface{}
  1050  		expectParam map[string]string
  1051  	}{
  1052  		{
  1053  			whenURL:     "/g/s",
  1054  			expectRoute: "/g/:name",
  1055  			expectParam: map[string]string{"name": "s"},
  1056  		},
  1057  		{
  1058  			whenURL:     "/g/status",
  1059  			expectRoute: "/g/status",
  1060  			expectParam: map[string]string{"name": ""},
  1061  		},
  1062  	}
  1063  	for _, tc := range testCases {
  1064  		t.Run(tc.whenURL, func(t *testing.T) {
  1065  			ctx := e.NewContext()
  1066  
  1067  			ctx.Request.SetRequestURI(tc.whenURL)
  1068  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1069  			e.ServeHTTP(context.Background(), ctx)
  1070  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1071  			for param, expectedValue := range tc.expectParam {
  1072  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1073  			}
  1074  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1075  		})
  1076  	}
  1077  }
  1078  
  1079  func TestRouterMatchAny(t *testing.T) {
  1080  	e := NewEngine(config.NewOptions(nil))
  1081  
  1082  	// Routes
  1083  	e.addRoute(consts.MethodGet, "/", handlerFunc("/"))
  1084  	e.addRoute(consts.MethodGet, "/*x", handlerFunc("/*x"))
  1085  	e.addRoute(consts.MethodGet, "/users/*x", handlerFunc("/users/*x"))
  1086  
  1087  	testCases := []struct {
  1088  		whenURL     string
  1089  		expectRoute interface{}
  1090  		expectParam map[string]string
  1091  	}{
  1092  		{
  1093  			whenURL:     "/",
  1094  			expectRoute: "/",
  1095  			expectParam: map[string]string{"x": ""},
  1096  		},
  1097  		{
  1098  			whenURL:     "/download",
  1099  			expectRoute: "/*x",
  1100  			expectParam: map[string]string{"x": "download"},
  1101  		},
  1102  		{
  1103  			whenURL:     "/users/joe",
  1104  			expectRoute: "/users/*x",
  1105  			expectParam: map[string]string{"x": "joe"},
  1106  		},
  1107  	}
  1108  	for _, tc := range testCases {
  1109  		t.Run(tc.whenURL, func(t *testing.T) {
  1110  			ctx := e.NewContext()
  1111  
  1112  			ctx.Request.SetRequestURI(tc.whenURL)
  1113  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1114  			e.ServeHTTP(context.Background(), ctx)
  1115  
  1116  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1117  			for param, expectedValue := range tc.expectParam {
  1118  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1119  			}
  1120  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1121  		})
  1122  	}
  1123  }
  1124  
  1125  // NOTE: This is to document current implementation. Last added route with `*` asterisk is always the match and no
  1126  // backtracking or more precise matching is done to find more suitable match.
  1127  //
  1128  // Current behaviour might not be correct or expected.
  1129  // But this is where we are without well defined requirements/rules how (multiple) asterisks work in route
  1130  func TestRouterAnyMatchesLastAddedAnyRoute(t *testing.T) {
  1131  	e := NewEngine(config.NewOptions(nil))
  1132  
  1133  	e.addRoute(consts.MethodGet, "/users/*x", handlerHelper("/users/*x", "case", 1))
  1134  	// e.addRoute(consts.MethodGet, "/users/*x/action*y", handlerHelper("/users/*x/action*y", "case", 2))
  1135  
  1136  	ctx := e.NewContext()
  1137  
  1138  	ctx.Request.SetRequestURI("/users/xxx/action/sea")
  1139  	ctx.Request.Header.SetMethod(consts.MethodGet)
  1140  	e.ServeHTTP(context.Background(), ctx)
  1141  	assert.DeepEqual(t, "/users/*x", getHelper(ctx, "path"))
  1142  	assert.DeepEqual(t, "xxx/action/sea", ctx.Param("x"))
  1143  
  1144  	// if we add another route then it is the last added and so it is matched
  1145  	// e.addRoute(consts.MethodGet, "/users/*x/action/search", handlerHelper("/users/*x/action/search", "case", 3))
  1146  
  1147  	// c.Request.SetRequestURI("/users/xxx/action/sea")
  1148  	// c.Request.Header.SetMethod(consts.MethodGet)
  1149  	// e.ServeHTTP(context.Background(), c)
  1150  	// test.DeepEqual(t, "/users/*x/action/search", getHelper(c, "path"))
  1151  	// test.DeepEqual(t, "xxx/action/sea", ctx.Param("x"))
  1152  }
  1153  
  1154  func TestRouterMatchAnyPrefixIssue(t *testing.T) {
  1155  	e := NewEngine(config.NewOptions(nil))
  1156  
  1157  	// Routes
  1158  	e.addRoute(consts.MethodGet, "/*x", handlerFunc("/*x"))
  1159  	e.addRoute(consts.MethodGet, "/users/*x", handlerFunc("/users/*x"))
  1160  
  1161  	testCases := []struct {
  1162  		whenURL     string
  1163  		expectRoute interface{}
  1164  		expectParam map[string]string
  1165  	}{
  1166  		{
  1167  			whenURL:     "/",
  1168  			expectRoute: "/*x",
  1169  			expectParam: map[string]string{"x": ""},
  1170  		},
  1171  		{
  1172  			whenURL:     "/users",
  1173  			expectRoute: "/*x",
  1174  			expectParam: map[string]string{"x": "users"},
  1175  		},
  1176  		{
  1177  			whenURL:     "/users/",
  1178  			expectRoute: "/users/*x",
  1179  			expectParam: map[string]string{"x": ""},
  1180  		},
  1181  		{
  1182  			whenURL:     "/users_prefix",
  1183  			expectRoute: "/*x",
  1184  			expectParam: map[string]string{"x": "users_prefix"},
  1185  		},
  1186  		{
  1187  			whenURL:     "/users_prefix/",
  1188  			expectRoute: "/*x",
  1189  			expectParam: map[string]string{"x": "users_prefix/"},
  1190  		},
  1191  	}
  1192  	for _, tc := range testCases {
  1193  		t.Run(tc.whenURL, func(t *testing.T) {
  1194  			ctx := e.NewContext()
  1195  
  1196  			ctx.Request.SetRequestURI(tc.whenURL)
  1197  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1198  			e.ServeHTTP(context.Background(), ctx)
  1199  
  1200  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1201  			for param, expectedValue := range tc.expectParam {
  1202  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1203  			}
  1204  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1205  		})
  1206  	}
  1207  }
  1208  
  1209  // TestRouterMatchAnySlash shall verify finding the best route
  1210  // for any routes with trailing slash requests
  1211  func TestRouterMatchAnySlash(t *testing.T) {
  1212  	e := NewEngine(config.NewOptions(nil))
  1213  
  1214  	// Routes
  1215  	e.addRoute(consts.MethodGet, "/users", handlerFunc("/users"))
  1216  	e.addRoute(consts.MethodGet, "/users/*x", handlerFunc("/users/*x"))
  1217  	e.addRoute(consts.MethodGet, "/img/*x", handlerFunc("/img/*x"))
  1218  	e.addRoute(consts.MethodGet, "/img/load", handlerFunc("/img/load"))
  1219  	e.addRoute(consts.MethodGet, "/img/load/*x", handlerFunc("/img/load/*x"))
  1220  	e.addRoute(consts.MethodGet, "/assets/*x", handlerFunc("/assets/*x"))
  1221  
  1222  	testCases := []struct {
  1223  		whenURL     string
  1224  		expectRoute interface{}
  1225  		expectParam map[string]string
  1226  		expectError error
  1227  	}{
  1228  		{
  1229  			whenURL:     "/",
  1230  			expectRoute: nil,
  1231  			expectParam: map[string]string{"x": ""},
  1232  		},
  1233  		{ // Test trailing slash request for simple any route (see #1526)
  1234  			whenURL:     "/users/",
  1235  			expectRoute: "/users/*x",
  1236  			expectParam: map[string]string{"x": ""},
  1237  		},
  1238  		{
  1239  			whenURL:     "/users/joe",
  1240  			expectRoute: "/users/*x",
  1241  			expectParam: map[string]string{"x": "joe"},
  1242  		},
  1243  		// Test trailing slash request for nested any route (see #1526)
  1244  		{
  1245  			whenURL:     "/img/load",
  1246  			expectRoute: "/img/load",
  1247  			expectParam: map[string]string{"x": ""},
  1248  		},
  1249  		{
  1250  			whenURL:     "/img/load/",
  1251  			expectRoute: "/img/load/*x",
  1252  			expectParam: map[string]string{"x": ""},
  1253  		},
  1254  		{
  1255  			whenURL:     "/img/load/ben",
  1256  			expectRoute: "/img/load/*x",
  1257  			expectParam: map[string]string{"x": "ben"},
  1258  		},
  1259  		// Test /assets/*x any route
  1260  		{ // ... without trailing slash must not match
  1261  			whenURL:     "/assets",
  1262  			expectRoute: nil,
  1263  			expectParam: map[string]string{"x": ""},
  1264  		},
  1265  
  1266  		{ // ... with trailing slash must match
  1267  			whenURL:     "/assets/",
  1268  			expectRoute: "/assets/*x",
  1269  			expectParam: map[string]string{"x": ""},
  1270  		},
  1271  	}
  1272  	for _, tc := range testCases {
  1273  		t.Run(tc.whenURL, func(t *testing.T) {
  1274  			ctx := e.NewContext()
  1275  
  1276  			ctx.Request.SetRequestURI(tc.whenURL)
  1277  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1278  			e.ServeHTTP(context.Background(), ctx)
  1279  
  1280  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1281  			for param, expectedValue := range tc.expectParam {
  1282  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1283  			}
  1284  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1285  		})
  1286  	}
  1287  }
  1288  
  1289  func TestRouterMatchAnyMultiLevel(t *testing.T) {
  1290  	e := NewEngine(config.NewOptions(nil))
  1291  
  1292  	// Routes
  1293  	e.addRoute(consts.MethodGet, "/api/users/jack", handlerFunc("/api/users/jack"))
  1294  	e.addRoute(consts.MethodGet, "/api/users/jill", handlerFunc("/api/users/jill"))
  1295  	e.addRoute(consts.MethodGet, "/api/users/*x", handlerFunc("/api/users/*x"))
  1296  	e.addRoute(consts.MethodGet, "/api/*x", handlerFunc("/api/*x"))
  1297  	e.addRoute(consts.MethodGet, "/other/*x", handlerFunc("/other/*x"))
  1298  	e.addRoute(consts.MethodGet, "/*x", handlerFunc("/*x"))
  1299  
  1300  	testCases := []struct {
  1301  		whenURL     string
  1302  		expectRoute interface{}
  1303  		expectParam map[string]string
  1304  		expectError error
  1305  	}{
  1306  		{
  1307  			whenURL:     "/api/users/jack",
  1308  			expectRoute: "/api/users/jack",
  1309  			expectParam: map[string]string{"x": ""},
  1310  		},
  1311  		{
  1312  			whenURL:     "/api/users/jill",
  1313  			expectRoute: "/api/users/jill",
  1314  			expectParam: map[string]string{"x": ""},
  1315  		},
  1316  		{
  1317  			whenURL:     "/api/users/joe",
  1318  			expectRoute: "/api/users/*x",
  1319  			expectParam: map[string]string{"x": "joe"},
  1320  		},
  1321  		{
  1322  			whenURL:     "/api/nousers/joe",
  1323  			expectRoute: "/api/*x",
  1324  			expectParam: map[string]string{"x": "nousers/joe"},
  1325  		},
  1326  		{
  1327  			whenURL:     "/api/none",
  1328  			expectRoute: "/api/*x",
  1329  			expectParam: map[string]string{"x": "none"},
  1330  		},
  1331  		{
  1332  			whenURL:     "/api/none",
  1333  			expectRoute: "/api/*x",
  1334  			expectParam: map[string]string{"x": "none"},
  1335  		},
  1336  		{
  1337  			whenURL:     "/noapi/users/jim",
  1338  			expectRoute: "/*x",
  1339  			expectParam: map[string]string{"x": "noapi/users/jim"},
  1340  		},
  1341  	}
  1342  	for _, tc := range testCases {
  1343  		t.Run(tc.whenURL, func(t *testing.T) {
  1344  			ctx := e.NewContext()
  1345  
  1346  			ctx.Request.SetRequestURI(tc.whenURL)
  1347  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1348  			e.ServeHTTP(context.Background(), ctx)
  1349  
  1350  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1351  			for param, expectedValue := range tc.expectParam {
  1352  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1353  			}
  1354  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1355  		})
  1356  	}
  1357  }
  1358  
  1359  func TestRouterMatchAnyMultiLevelWithPost(t *testing.T) {
  1360  	e := NewEngine(config.NewOptions(nil))
  1361  
  1362  	// Routes
  1363  	e.POST("/api/auth/login", handlerFunc("/api/auth/login")...)
  1364  	e.POST("/api/auth/forgotPassword", handlerFunc("/api/auth/forgotPassword")...)
  1365  	e.Any("/api/*x", handlerFunc("/api/*x")...)
  1366  	e.Any("/*x", handlerFunc("/*x")...)
  1367  
  1368  	testCases := []struct {
  1369  		whenMethod  string
  1370  		whenURL     string
  1371  		expectRoute interface{}
  1372  		expectParam map[string]string
  1373  		expectError error
  1374  	}{
  1375  		{ // POST /api/auth/login shall choose login method
  1376  			whenURL:     "/api/auth/login",
  1377  			whenMethod:  consts.MethodPost,
  1378  			expectRoute: "/api/auth/login",
  1379  			expectParam: map[string]string{"x": ""},
  1380  		},
  1381  		{ // POST /api/auth/logout shall choose nearest any route
  1382  			whenURL:     "/api/auth/logout",
  1383  			whenMethod:  consts.MethodPost,
  1384  			expectRoute: "/api/*x",
  1385  			expectParam: map[string]string{"x": "auth/logout"},
  1386  		},
  1387  		{ // POST to /api/other/test shall choose nearest any route
  1388  			whenURL:     "/api/other/test",
  1389  			whenMethod:  consts.MethodPost,
  1390  			expectRoute: "/api/*x",
  1391  			expectParam: map[string]string{"x": "other/test"},
  1392  		},
  1393  		{ // GET to /api/other/test shall choose nearest any route
  1394  			whenURL:     "/api/other/test",
  1395  			whenMethod:  consts.MethodGet,
  1396  			expectRoute: "/api/*x",
  1397  			expectParam: map[string]string{"x": "other/test"},
  1398  		},
  1399  	}
  1400  	for _, tc := range testCases {
  1401  		t.Run(tc.whenURL, func(t *testing.T) {
  1402  			ctx := e.NewContext()
  1403  			ctx.Request.SetRequestURI(tc.whenURL)
  1404  			ctx.Request.Header.SetMethod(tc.whenMethod)
  1405  			e.ServeHTTP(context.Background(), ctx)
  1406  
  1407  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1408  			for param, expectedValue := range tc.expectParam {
  1409  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1410  			}
  1411  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1412  		})
  1413  	}
  1414  }
  1415  
  1416  func TestRouterMicroParam(t *testing.T) {
  1417  	e := NewEngine(config.NewOptions(nil))
  1418  	e.addRoute(consts.MethodGet, "/:a/:b/:c", handlerFunc("/:a/:b/:c"))
  1419  	ctx := e.NewContext()
  1420  	ctx.Request.SetRequestURI("/1/2/3")
  1421  	ctx.Request.Header.SetMethod(consts.MethodGet)
  1422  	e.ServeHTTP(context.Background(), ctx)
  1423  	assert.DeepEqual(t, "1", ctx.Param("a"))
  1424  	assert.DeepEqual(t, "2", ctx.Param("b"))
  1425  	assert.DeepEqual(t, "3", ctx.Param("c"))
  1426  }
  1427  
  1428  func TestRouterMixParamMatchAny(t *testing.T) {
  1429  	e := NewEngine(config.NewOptions(nil))
  1430  
  1431  	// Route
  1432  	e.addRoute(consts.MethodGet, "/users/:id/*x", handlerFunc("/users/:id/*x"))
  1433  	ctx := e.NewContext()
  1434  
  1435  	ctx.Request.SetRequestURI("/users/joe/comments")
  1436  	ctx.Request.Header.SetMethod(consts.MethodGet)
  1437  	e.ServeHTTP(context.Background(), ctx)
  1438  	assert.DeepEqual(t, "joe", ctx.Param("id"))
  1439  }
  1440  
  1441  func TestRouterMultiRoute(t *testing.T) {
  1442  	e := NewEngine(config.NewOptions(nil))
  1443  
  1444  	// Routes
  1445  	e.addRoute(consts.MethodGet, "/users", handlerFunc("/users"))
  1446  	e.addRoute(consts.MethodGet, "/users/:id", handlerFunc("/users/:id"))
  1447  
  1448  	testCases := []struct {
  1449  		whenMethod  string
  1450  		whenURL     string
  1451  		expectRoute interface{}
  1452  		expectParam map[string]string
  1453  		expectError error
  1454  	}{
  1455  		{
  1456  			whenURL:     "/users",
  1457  			expectRoute: "/users",
  1458  			expectParam: map[string]string{"x": ""},
  1459  		},
  1460  		{
  1461  			whenURL:     "/users/1",
  1462  			expectRoute: "/users/:id",
  1463  			expectParam: map[string]string{"id": "1"},
  1464  		},
  1465  		{
  1466  			whenURL:     "/user",
  1467  			expectRoute: nil,
  1468  			expectParam: map[string]string{"x": ""},
  1469  		},
  1470  	}
  1471  	for _, tc := range testCases {
  1472  		t.Run(tc.whenURL, func(t *testing.T) {
  1473  			ctx := e.NewContext()
  1474  			ctx.Request.SetRequestURI(tc.whenURL)
  1475  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1476  			e.ServeHTTP(context.Background(), ctx)
  1477  
  1478  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1479  			for param, expectedValue := range tc.expectParam {
  1480  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1481  			}
  1482  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1483  		})
  1484  	}
  1485  }
  1486  
  1487  func TestRouterPriority(t *testing.T) {
  1488  	e := NewEngine(config.NewOptions(nil))
  1489  
  1490  	// Routes
  1491  	e.addRoute(consts.MethodGet, "/users", handlerFunc("/users"))
  1492  	e.addRoute(consts.MethodGet, "/users/new", handlerFunc("/users/new"))
  1493  	e.addRoute(consts.MethodGet, "/users/:id", handlerFunc("/users/:id"))
  1494  	e.addRoute(consts.MethodGet, "/users/dew", handlerFunc("/users/dew"))
  1495  	e.addRoute(consts.MethodGet, "/users/:id/files", handlerFunc("/users/:id/files"))
  1496  	e.addRoute(consts.MethodGet, "/users/newsee", handlerFunc("/users/newsee"))
  1497  	e.addRoute(consts.MethodGet, "/users/*x", handlerFunc("/users/*x"))
  1498  	e.addRoute(consts.MethodGet, "/users/new/*x", handlerFunc("/users/new/*x"))
  1499  	e.addRoute(consts.MethodGet, "/*x", handlerFunc("/*x"))
  1500  
  1501  	testCases := []struct {
  1502  		whenMethod  string
  1503  		whenURL     string
  1504  		expectRoute interface{}
  1505  		expectParam map[string]string
  1506  		expectError error
  1507  	}{
  1508  		{
  1509  			whenURL:     "/users",
  1510  			expectRoute: "/users",
  1511  		},
  1512  		{
  1513  			whenURL:     "/users/new",
  1514  			expectRoute: "/users/new",
  1515  		},
  1516  		{
  1517  			whenURL:     "/users/1",
  1518  			expectRoute: "/users/:id",
  1519  			expectParam: map[string]string{"id": "1"},
  1520  		},
  1521  		{
  1522  			whenURL:     "/users/dew",
  1523  			expectRoute: "/users/dew",
  1524  		},
  1525  		{
  1526  			whenURL:     "/users/1/files",
  1527  			expectRoute: "/users/:id/files",
  1528  			expectParam: map[string]string{"id": "1"},
  1529  		},
  1530  		{
  1531  			whenURL:     "/users/new",
  1532  			expectRoute: "/users/new",
  1533  		},
  1534  		{
  1535  			whenURL:     "/users/news",
  1536  			expectRoute: "/users/:id",
  1537  			expectParam: map[string]string{"id": "news"},
  1538  		},
  1539  		{
  1540  			whenURL:     "/users/newsee",
  1541  			expectRoute: "/users/newsee",
  1542  		},
  1543  		{
  1544  			whenURL:     "/users/joe/books",
  1545  			expectRoute: "/users/*x",
  1546  			expectParam: map[string]string{"x": "joe/books"},
  1547  		},
  1548  		{
  1549  			whenURL:     "/users/new/someone",
  1550  			expectRoute: "/users/new/*x",
  1551  			expectParam: map[string]string{"x": "someone"},
  1552  		},
  1553  		{
  1554  			whenURL:     "/users/dew/someone",
  1555  			expectRoute: "/users/*x",
  1556  			expectParam: map[string]string{"x": "dew/someone"},
  1557  		},
  1558  		{ // Route > /users/*x should be matched although /users/dew exists
  1559  			whenURL:     "/users/notexists/someone",
  1560  			expectRoute: "/users/*x",
  1561  			expectParam: map[string]string{"x": "notexists/someone"},
  1562  		},
  1563  		{
  1564  			whenURL:     "/nousers",
  1565  			expectRoute: "/*x",
  1566  			expectParam: map[string]string{"x": "nousers"},
  1567  		},
  1568  		{
  1569  			whenURL:     "/nousers/new",
  1570  			expectRoute: "/*x",
  1571  			expectParam: map[string]string{"x": "nousers/new"},
  1572  		},
  1573  	}
  1574  	for _, tc := range testCases {
  1575  		t.Run(tc.whenURL, func(t *testing.T) {
  1576  			ctx := e.NewContext()
  1577  			ctx.Request.SetRequestURI(tc.whenURL)
  1578  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1579  			e.ServeHTTP(context.Background(), ctx)
  1580  
  1581  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1582  			for param, expectedValue := range tc.expectParam {
  1583  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1584  			}
  1585  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1586  		})
  1587  	}
  1588  }
  1589  
  1590  func TestRouterIssue1348(t *testing.T) {
  1591  	e := NewEngine(config.NewOptions(nil))
  1592  
  1593  	e.addRoute(consts.MethodGet, "/:lang/", handlerFunc("/:lang/"))
  1594  	e.addRoute(consts.MethodGet, "/:lang/dupa", handlerFunc("/:lang/dupa"))
  1595  }
  1596  
  1597  func TestRouterPriorityNotFound(t *testing.T) {
  1598  	e := NewEngine(config.NewOptions(nil))
  1599  
  1600  	// Add
  1601  	e.addRoute(consts.MethodGet, "/a/foo", handlerFunc("/a/foo"))
  1602  	e.addRoute(consts.MethodGet, "/a/bar", handlerFunc("/a/bar"))
  1603  
  1604  	testCases := []struct {
  1605  		whenMethod  string
  1606  		whenURL     string
  1607  		expectRoute interface{}
  1608  		expectParam map[string]string
  1609  		expectError error
  1610  	}{
  1611  		{
  1612  			whenURL:     "/a/foo",
  1613  			expectRoute: "/a/foo",
  1614  		},
  1615  		{
  1616  			whenURL:     "/a/bar",
  1617  			expectRoute: "/a/bar",
  1618  		},
  1619  		{
  1620  			whenURL:     "/abc/def",
  1621  			expectRoute: nil,
  1622  		},
  1623  	}
  1624  	for _, tc := range testCases {
  1625  		t.Run(tc.whenURL, func(t *testing.T) {
  1626  			ctx := e.NewContext()
  1627  			ctx.Request.SetRequestURI(tc.whenURL)
  1628  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1629  			e.ServeHTTP(context.Background(), ctx)
  1630  
  1631  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1632  			for param, expectedValue := range tc.expectParam {
  1633  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1634  			}
  1635  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1636  		})
  1637  	}
  1638  }
  1639  
  1640  func TestRouterParamNames(t *testing.T) {
  1641  	e := NewEngine(config.NewOptions(nil))
  1642  
  1643  	// Routes
  1644  	e.addRoute(consts.MethodGet, "/users", handlerFunc("/users"))
  1645  	e.addRoute(consts.MethodGet, "/users/:id", handlerFunc("/users/:id"))
  1646  	e.addRoute(consts.MethodGet, "/users/:uid/files/:fid", handlerFunc("/users/:uid/files/:fid"))
  1647  
  1648  	testCases := []struct {
  1649  		whenMethod  string
  1650  		whenURL     string
  1651  		expectRoute interface{}
  1652  		expectParam map[string]string
  1653  		expectError error
  1654  	}{
  1655  		{
  1656  			whenURL:     "/users",
  1657  			expectRoute: "/users",
  1658  		},
  1659  		{
  1660  			whenURL:     "/users/1",
  1661  			expectRoute: "/users/:id",
  1662  			expectParam: map[string]string{"id": "1"},
  1663  		},
  1664  		{
  1665  			whenURL:     "/users/1/files/1",
  1666  			expectRoute: "/users/:uid/files/:fid",
  1667  			expectParam: map[string]string{
  1668  				"uid": "1",
  1669  				"fid": "1",
  1670  			},
  1671  		},
  1672  	}
  1673  	for _, tc := range testCases {
  1674  		t.Run(tc.whenURL, func(t *testing.T) {
  1675  			ctx := e.NewContext()
  1676  			ctx.Request.SetRequestURI(tc.whenURL)
  1677  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1678  			e.ServeHTTP(context.Background(), ctx)
  1679  
  1680  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1681  			for param, expectedValue := range tc.expectParam {
  1682  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1683  			}
  1684  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1685  		})
  1686  	}
  1687  }
  1688  
  1689  func TestRouterStaticDynamicConflict(t *testing.T) {
  1690  	e := NewEngine(config.NewOptions(nil))
  1691  
  1692  	e.addRoute(consts.MethodGet, "/dictionary/skills", handlerHelper("/dictionary/skills", "a", 1))
  1693  	e.addRoute(consts.MethodGet, "/dictionary/:name", handlerHelper("/dictionary/:name", "b", 2))
  1694  	e.addRoute(consts.MethodGet, "/users/new", handlerHelper("/users/new", "d", 4))
  1695  	e.addRoute(consts.MethodGet, "/users/:name", handlerHelper("/users/:name", "e", 5))
  1696  	e.addRoute(consts.MethodGet, "/server", handlerHelper("/server", "c", 3))
  1697  	e.addRoute(consts.MethodGet, "/", handlerHelper("/", "f", 6))
  1698  
  1699  	testCases := []struct {
  1700  		whenMethod  string
  1701  		whenURL     string
  1702  		expectRoute interface{}
  1703  		expectParam map[string]string
  1704  		expectError error
  1705  	}{
  1706  		{
  1707  			whenURL:     "/dictionary/skills",
  1708  			expectRoute: "/dictionary/skills",
  1709  			expectParam: map[string]string{"x": ""},
  1710  		},
  1711  		{
  1712  			whenURL:     "/dictionary/skillsnot",
  1713  			expectRoute: "/dictionary/:name",
  1714  			expectParam: map[string]string{"name": "skillsnot"},
  1715  		},
  1716  		{
  1717  			whenURL:     "/dictionary/type",
  1718  			expectRoute: "/dictionary/:name",
  1719  			expectParam: map[string]string{"name": "type"},
  1720  		},
  1721  		{
  1722  			whenURL:     "/server",
  1723  			expectRoute: "/server",
  1724  		},
  1725  		{
  1726  			whenURL:     "/users/new",
  1727  			expectRoute: "/users/new",
  1728  		},
  1729  		{
  1730  			whenURL:     "/users/new2",
  1731  			expectRoute: "/users/:name",
  1732  			expectParam: map[string]string{"name": "new2"},
  1733  		},
  1734  		{
  1735  			whenURL:     "/",
  1736  			expectRoute: "/",
  1737  		},
  1738  	}
  1739  	for _, tc := range testCases {
  1740  		t.Run(tc.whenURL, func(t *testing.T) {
  1741  			ctx := e.NewContext()
  1742  			ctx.Request.SetRequestURI(tc.whenURL)
  1743  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1744  			e.ServeHTTP(context.Background(), ctx)
  1745  
  1746  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1747  			for param, expectedValue := range tc.expectParam {
  1748  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1749  			}
  1750  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1751  		})
  1752  	}
  1753  }
  1754  
  1755  func TestRouterParamBacktraceNotFound(t *testing.T) {
  1756  	e := NewEngine(config.NewOptions(nil))
  1757  
  1758  	// Add
  1759  	e.addRoute(consts.MethodGet, "/:param1", handlerFunc("/:param1"))
  1760  	e.addRoute(consts.MethodGet, "/:param1/foo", handlerFunc("/:param1/foo"))
  1761  	e.addRoute(consts.MethodGet, "/:param1/bar", handlerFunc("/:param1/bar"))
  1762  	e.addRoute(consts.MethodGet, "/:param1/bar/:param2", handlerFunc("/:param1/bar/:param2"))
  1763  
  1764  	testCases := []struct {
  1765  		name        string
  1766  		whenMethod  string
  1767  		whenURL     string
  1768  		expectRoute interface{}
  1769  		expectParam map[string]string
  1770  		expectError error
  1771  	}{
  1772  		{
  1773  			name:        "route /a to /:param1",
  1774  			whenURL:     "/a",
  1775  			expectRoute: "/:param1",
  1776  			expectParam: map[string]string{"param1": "a"},
  1777  		},
  1778  		{
  1779  			name:        "route /a/foo to /:param1/foo",
  1780  			whenURL:     "/a/foo",
  1781  			expectRoute: "/:param1/foo",
  1782  			expectParam: map[string]string{"param1": "a"},
  1783  		},
  1784  		{
  1785  			name:        "route /a/bar to /:param1/bar",
  1786  			whenURL:     "/a/bar",
  1787  			expectRoute: "/:param1/bar",
  1788  			expectParam: map[string]string{"param1": "a"},
  1789  		},
  1790  		{
  1791  			name:        "route /a/bar/b to /:param1/bar/:param2",
  1792  			whenURL:     "/a/bar/b",
  1793  			expectRoute: "/:param1/bar/:param2",
  1794  			expectParam: map[string]string{
  1795  				"param1": "a",
  1796  				"param2": "b",
  1797  			},
  1798  		},
  1799  		{
  1800  			name:        "route /a/bbbbb should return 404",
  1801  			whenURL:     "/a/bbbbb",
  1802  			expectRoute: nil,
  1803  		},
  1804  	}
  1805  	for _, tc := range testCases {
  1806  		t.Run(tc.name, func(t *testing.T) {
  1807  			ctx := e.NewContext()
  1808  			ctx.Request.SetRequestURI(tc.whenURL)
  1809  			ctx.Request.Header.SetMethod(consts.MethodGet)
  1810  			e.ServeHTTP(context.Background(), ctx)
  1811  
  1812  			assert.DeepEqual(t, tc.expectRoute, getHelper(ctx, "path"))
  1813  			for param, expectedValue := range tc.expectParam {
  1814  				assert.DeepEqual(t, expectedValue, ctx.Param(param))
  1815  			}
  1816  			checkUnusedParamValues(t, ctx, tc.expectParam)
  1817  		})
  1818  	}
  1819  }