gin/routes_test.go
Kuroda Kayn 6bc76c3404 test(router): cover AIP custom verb routes
Custom verb support needs coverage beyond the tree insertion changes.

Add tree and engine tests for literal colon verbs and params followed by verb suffixes.

The assertions cover handler selection, FullPath, Param values, and parameter counting.
2026-06-28 16:25:48 +08:00

896 lines
28 KiB
Go

// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type header struct {
Key string
Value string
}
// PerformRequest for testing gin router.
func PerformRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, nil)
for _, h := range headers {
req.Header.Add(h.Key, h.Value)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
func testRouteOK(method string, t *testing.T) {
passed := false
passedAny := false
r := New()
r.Any("/test2", func(c *Context) {
passedAny = true
})
r.Handle(method, "/test", func(c *Context) {
passed = true
})
w := PerformRequest(r, method, "/test")
assert.True(t, passed)
assert.Equal(t, http.StatusOK, w.Code)
PerformRequest(r, method, "/test2")
assert.True(t, passedAny)
}
// TestSingleRouteOK tests that POST route is correctly invoked.
func testRouteNotOK(method string, t *testing.T) {
passed := false
router := New()
router.Handle(method, "/test_2", func(c *Context) {
passed = true
})
w := PerformRequest(router, method, "/test")
assert.False(t, passed)
assert.Equal(t, http.StatusNotFound, w.Code)
}
// TestSingleRouteOK tests that POST route is correctly invoked.
func testRouteNotOK2(method string, t *testing.T) {
passed := false
router := New()
router.HandleMethodNotAllowed = true
var methodRoute string
if method == http.MethodPost {
methodRoute = http.MethodGet
} else {
methodRoute = http.MethodPost
}
router.Handle(methodRoute, "/test", func(c *Context) {
passed = true
})
w := PerformRequest(router, method, "/test")
assert.False(t, passed)
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
}
func TestRouterMethod(t *testing.T) {
router := New()
router.PUT("/hey2", func(c *Context) {
c.String(http.StatusOK, "sup2")
})
router.PUT("/hey", func(c *Context) {
c.String(http.StatusOK, "called")
})
router.PUT("/hey3", func(c *Context) {
c.String(http.StatusOK, "sup3")
})
w := PerformRequest(router, http.MethodPut, "/hey")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "called", w.Body.String())
}
func TestRouterGroupRouteOK(t *testing.T) {
testRouteOK(http.MethodGet, t)
testRouteOK(http.MethodPost, t)
testRouteOK(http.MethodPut, t)
testRouteOK(http.MethodPatch, t)
testRouteOK(http.MethodHead, t)
testRouteOK(http.MethodOptions, t)
testRouteOK(http.MethodDelete, t)
testRouteOK(http.MethodConnect, t)
testRouteOK(http.MethodTrace, t)
}
func TestRouteNotOK(t *testing.T) {
testRouteNotOK(http.MethodGet, t)
testRouteNotOK(http.MethodPost, t)
testRouteNotOK(http.MethodPut, t)
testRouteNotOK(http.MethodPatch, t)
testRouteNotOK(http.MethodHead, t)
testRouteNotOK(http.MethodOptions, t)
testRouteNotOK(http.MethodDelete, t)
testRouteNotOK(http.MethodConnect, t)
testRouteNotOK(http.MethodTrace, t)
}
func TestRouteNotOK2(t *testing.T) {
testRouteNotOK2(http.MethodGet, t)
testRouteNotOK2(http.MethodPost, t)
testRouteNotOK2(http.MethodPut, t)
testRouteNotOK2(http.MethodPatch, t)
testRouteNotOK2(http.MethodHead, t)
testRouteNotOK2(http.MethodOptions, t)
testRouteNotOK2(http.MethodDelete, t)
testRouteNotOK2(http.MethodConnect, t)
testRouteNotOK2(http.MethodTrace, t)
}
func TestRouteRedirectTrailingSlash(t *testing.T) {
router := New()
router.RedirectFixedPath = false
router.RedirectTrailingSlash = true
router.GET("/path", func(c *Context) {})
router.GET("/path2/", func(c *Context) {})
router.POST("/path3", func(c *Context) {})
router.PUT("/path4/", func(c *Context) {})
w := PerformRequest(router, http.MethodGet, "/path/")
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, "/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodPost, "/path3/")
assert.Equal(t, "/path3", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = PerformRequest(router, http.MethodPut, "/path4")
assert.Equal(t, "/path4/", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusOK, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2/")
assert.Equal(t, http.StatusOK, w.Code)
w = PerformRequest(router, http.MethodPost, "/path3")
assert.Equal(t, http.StatusOK, w.Code)
w = PerformRequest(router, http.MethodPut, "/path4/")
assert.Equal(t, http.StatusOK, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
assert.Equal(t, http.StatusOK, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"})
assert.Equal(t, "/api/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"})
assert.Equal(t, "/api/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../api"})
assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../api"})
assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"})
assert.Equal(t, "//path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"})
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"})
assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"})
assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"})
assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"})
assert.Equal(t, "api/api/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"})
assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"})
assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
router.RedirectTrailingSlash = false
w = PerformRequest(router, http.MethodGet, "/path/")
assert.Equal(t, http.StatusNotFound, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, http.StatusNotFound, w.Code)
w = PerformRequest(router, http.MethodPost, "/path3/")
assert.Equal(t, http.StatusNotFound, w.Code)
w = PerformRequest(router, http.MethodPut, "/path4")
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouteRedirectFixedPath(t *testing.T) {
router := New()
router.RedirectFixedPath = true
router.RedirectTrailingSlash = false
router.GET("/path", func(c *Context) {})
router.GET("/Path2", func(c *Context) {})
router.POST("/PATH3", func(c *Context) {})
router.POST("/Path4/", func(c *Context) {})
w := PerformRequest(router, http.MethodGet, "/PATH")
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, "/Path2", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodPost, "/path3")
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = PerformRequest(router, http.MethodPost, "/path4")
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
}
// TestContextParamsGet tests that a parameter can be parsed from the URL.
func TestRouteParamsByName(t *testing.T) {
name := ""
lastName := ""
wild := ""
router := New()
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
name = c.Params.ByName("name")
lastName = c.Params.ByName("last_name")
var ok bool
wild, ok = c.Params.Get("wild")
assert.True(t, ok)
assert.Equal(t, name, c.Param("name"))
assert.Equal(t, lastName, c.Param("last_name"))
assert.Empty(t, c.Param("wtf"))
assert.Empty(t, c.Params.ByName("wtf"))
wtf, ok := c.Params.Get("wtf")
assert.Empty(t, wtf)
assert.False(t, ok)
})
w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name)
assert.Equal(t, "smith", lastName)
assert.Equal(t, "/is/super/great", wild)
}
// TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes.
func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
name := ""
lastName := ""
wild := ""
router := New()
router.RemoveExtraSlash = true
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
name = c.Params.ByName("name")
lastName = c.Params.ByName("last_name")
var ok bool
wild, ok = c.Params.Get("wild")
assert.True(t, ok)
assert.Equal(t, name, c.Param("name"))
assert.Equal(t, lastName, c.Param("last_name"))
assert.Empty(t, c.Param("wtf"))
assert.Empty(t, c.Params.ByName("wtf"))
wtf, ok := c.Params.Get("wtf")
assert.Empty(t, wtf)
assert.False(t, ok)
})
w := PerformRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name)
assert.Equal(t, "smith", lastName)
assert.Equal(t, "/is/super/great", wild)
}
// TestRouteParamsNotEmpty tests that context parameters will be set
// even if a route with params/wildcards is registered after the context
// initialisation (which happened in a previous requests).
func TestRouteParamsNotEmpty(t *testing.T) {
name := ""
lastName := ""
wild := ""
router := New()
w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
assert.Equal(t, http.StatusNotFound, w.Code)
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
name = c.Params.ByName("name")
lastName = c.Params.ByName("last_name")
var ok bool
wild, ok = c.Params.Get("wild")
assert.True(t, ok)
assert.Equal(t, name, c.Param("name"))
assert.Equal(t, lastName, c.Param("last_name"))
assert.Empty(t, c.Param("wtf"))
assert.Empty(t, c.Params.ByName("wtf"))
wtf, ok := c.Params.Get("wtf")
assert.Empty(t, wtf)
assert.False(t, ok)
})
w = PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name)
assert.Equal(t, "smith", lastName)
assert.Equal(t, "/is/super/great", wild)
}
// TestHandleStaticFile - ensure the static file handles properly
func TestRouteStaticFile(t *testing.T) {
// SETUP file
testRoot, _ := os.Getwd()
f, err := os.CreateTemp(testRoot, "")
if err != nil {
t.Error(err)
}
defer os.Remove(f.Name())
_, err = f.WriteString("Gin Web Framework")
require.NoError(t, err)
f.Close()
dir, filename := filepath.Split(f.Name())
// SETUP gin
router := New()
router.Static("/using_static", dir)
router.StaticFile("/result", f.Name())
w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
w2 := PerformRequest(router, http.MethodGet, "/result")
assert.Equal(t, w, w2)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "Gin Web Framework", w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
w4 := PerformRequest(router, http.MethodHead, "/result")
assert.Equal(t, w3, w4)
assert.Equal(t, http.StatusOK, w3.Code)
}
// TestHandleStaticFile - ensure the static file handles properly
func TestRouteStaticFileFS(t *testing.T) {
// SETUP file
testRoot, _ := os.Getwd()
f, err := os.CreateTemp(testRoot, "")
if err != nil {
t.Error(err)
}
defer os.Remove(f.Name())
_, err = f.WriteString("Gin Web Framework")
require.NoError(t, err)
f.Close()
dir, filename := filepath.Split(f.Name())
// SETUP gin
router := New()
router.Static("/using_static", dir)
router.StaticFileFS("/result_fs", filename, Dir(dir, false))
w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
w2 := PerformRequest(router, http.MethodGet, "/result_fs")
assert.Equal(t, w, w2)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "Gin Web Framework", w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
w4 := PerformRequest(router, http.MethodHead, "/result_fs")
assert.Equal(t, w3, w4)
assert.Equal(t, http.StatusOK, w3.Code)
}
// TestHandleStaticDir - ensure the root/sub dir handles properly
func TestRouteStaticListingDir(t *testing.T) {
router := New()
router.StaticFS("/", Dir("./", true))
w := PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "gin.go")
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestHandleHeadToDir - ensure the root/sub dir handles properly
func TestRouteStaticNoListing(t *testing.T) {
router := New()
router.Static("/", "./")
w := PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusNotFound, w.Code)
assert.NotContains(t, w.Body.String(), "gin.go")
}
func TestRouterMiddlewareAndStatic(t *testing.T) {
router := New()
static := router.Group("/", func(c *Context) {
c.Writer.Header().Add("Last-Modified", "Mon, 02 Jan 2006 15:04:05 MST")
c.Writer.Header().Add("Expires", "Mon, 02 Jan 2006 15:04:05 MST")
c.Writer.Header().Add("X-GIN", "Gin Framework")
})
static.Static("/", "./")
w := PerformRequest(router, http.MethodGet, "/gin.go")
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "package gin")
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
// else, Content-Type='text/x-go; charset=utf-8'
assert.NotEmpty(t, w.Header().Get("Content-Type"))
assert.NotEqual(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Last-Modified"))
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
}
func TestRouteNotAllowedEnabled(t *testing.T) {
router := New()
router.HandleMethodNotAllowed = true
router.POST("/path", func(c *Context) {})
w := PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText")
})
w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, "responseText", w.Body.String())
assert.Equal(t, http.StatusTeapot, w.Code)
}
func TestRouteNotAllowedEnabled2(t *testing.T) {
router := New()
router.HandleMethodNotAllowed = true
// add one methodTree to trees
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
router.GET("/path2", func(c *Context) {})
w := PerformRequest(router, http.MethodPost, "/path2")
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
}
func TestRouteNotAllowedEnabled3(t *testing.T) {
router := New()
router.HandleMethodNotAllowed = true
router.GET("/path", func(c *Context) {})
router.POST("/path", func(c *Context) {})
w := PerformRequest(router, http.MethodPut, "/path")
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
allowed := w.Header().Get("Allow")
assert.Contains(t, allowed, http.MethodGet)
assert.Contains(t, allowed, http.MethodPost)
}
func TestRouteNotAllowedDisabled(t *testing.T) {
router := New()
router.HandleMethodNotAllowed = false
router.POST("/path", func(c *Context) {})
w := PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusNotFound, w.Code)
router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText")
})
w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, "404 page not found", w.Body.String())
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
router := New()
router.RemoveExtraSlash = true
router.GET("/path", func(c *Context) {})
router.GET("/", func(c *Context) {})
testRoutes := []struct {
route string
code int
location string
}{
{"/../path", http.StatusOK, ""}, // CleanPath
{"/nope", http.StatusNotFound, ""}, // NotFound
}
for _, tr := range testRoutes {
w := PerformRequest(router, http.MethodGet, tr.route)
assert.Equal(t, tr.code, w.Code)
if w.Code != http.StatusNotFound {
assert.Equal(t, tr.location, w.Header().Get("Location"))
}
}
}
func TestRouterNotFound(t *testing.T) {
router := New()
router.RedirectFixedPath = true
router.GET("/path", func(c *Context) {})
router.GET("/dir/", func(c *Context) {})
router.GET("/", func(c *Context) {})
testRoutes := []struct {
route string
code int
location string
}{
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
{"/../path", http.StatusMovedPermanently, "/path"}, // Without CleanPath
{"/nope", http.StatusNotFound, ""}, // NotFound
}
for _, tr := range testRoutes {
w := PerformRequest(router, http.MethodGet, tr.route)
assert.Equal(t, tr.code, w.Code)
if w.Code != http.StatusNotFound {
assert.Equal(t, tr.location, w.Header().Get("Location"))
}
}
// Test custom not found handler
var notFound bool
router.NoRoute(func(c *Context) {
c.AbortWithStatus(http.StatusNotFound)
notFound = true
})
w := PerformRequest(router, http.MethodGet, "/nope")
assert.Equal(t, http.StatusNotFound, w.Code)
assert.True(t, notFound)
// Test other method than GET (want 307 instead of 301)
router.PATCH("/path", func(c *Context) {})
w = PerformRequest(router, http.MethodPatch, "/path/")
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
// Test special case where no node for the prefix "/" exists
router = New()
router.GET("/a", func(c *Context) {})
w = PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusNotFound, w.Code)
// Reproduction test for the bug of issue #2843
router = New()
router.NoRoute(func(c *Context) {
if c.Request.RequestURI == "/login" {
c.String(http.StatusOK, "login")
}
})
router.GET("/logout", func(c *Context) {
c.String(http.StatusOK, "logout")
})
w = PerformRequest(router, http.MethodGet, "/login")
assert.Equal(t, "login", w.Body.String())
w = PerformRequest(router, http.MethodGet, "/logout")
assert.Equal(t, "logout", w.Body.String())
}
func TestRouterStaticFSNotFound(t *testing.T) {
router := New()
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
router.NoRoute(func(c *Context) {
c.String(http.StatusNotFound, "non existent")
})
w := PerformRequest(router, http.MethodGet, "/nonexistent")
assert.Equal(t, "non existent", w.Body.String())
w = PerformRequest(router, http.MethodHead, "/nonexistent")
assert.Equal(t, "non existent", w.Body.String())
}
func TestRouterStaticFSFileNotFound(t *testing.T) {
router := New()
router.StaticFS("/", http.FileSystem(http.Dir(".")))
assert.NotPanics(t, func() {
PerformRequest(router, http.MethodGet, "/nonexistent")
})
}
// Reproduction test for the bug of issue #1805
func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
router := New()
// Middleware must be called just only once by per request.
middlewareCalledNum := 0
router.Use(func(c *Context) {
middlewareCalledNum++
})
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
// First access
PerformRequest(router, http.MethodGet, "/nonexistent")
assert.Equal(t, 1, middlewareCalledNum)
// Second access
PerformRequest(router, http.MethodHead, "/nonexistent")
assert.Equal(t, 2, middlewareCalledNum)
}
func TestRouteRawPath(t *testing.T) {
route := New()
route.UseRawPath = true
route.POST("/project/:name/build/:num", func(c *Context) {
name := c.Params.ByName("name")
num := c.Params.ByName("num")
assert.Equal(t, name, c.Param("name"))
assert.Equal(t, num, c.Param("num"))
assert.Equal(t, "Some/Other/Project", name)
assert.Equal(t, "222", num)
})
w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestRouteRawPathNoUnescape(t *testing.T) {
route := New()
route.UseRawPath = true
route.UnescapePathValues = false
route.POST("/project/:name/build/:num", func(c *Context) {
name := c.Params.ByName("name")
num := c.Params.ByName("num")
assert.Equal(t, name, c.Param("name"))
assert.Equal(t, num, c.Param("num"))
assert.Equal(t, "Some%2FOther%2FProject", name)
assert.Equal(t, "333", num)
})
w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
route := New()
route.Use(func(c *Context) {
c.Status(http.StatusMisdirectedRequest)
c.Next()
})
w := PerformRequest(route, http.MethodGet, "/NotFound")
assert.Equal(t, http.StatusMisdirectedRequest, w.Code)
assert.Equal(t, 0, w.Body.Len())
}
func TestRouteContextHoldsFullPath(t *testing.T) {
router := New()
// Test routes
routes := []string{
"/simple",
"/project/:name",
"/",
"/news/home",
"/news",
"/simple-two/one",
"/simple-two/one-two",
"/project/:name/build/*params",
"/project/:name/bui",
"/user/:id/status",
"/user/:id",
"/user/:id/profile",
}
for _, route := range routes {
actualRoute := route
router.GET(route, func(c *Context) {
// For each defined route context should contain its full path
assert.Equal(t, actualRoute, c.FullPath())
c.AbortWithStatus(http.StatusOK)
})
}
for _, route := range routes {
w := PerformRequest(router, http.MethodGet, route)
assert.Equal(t, http.StatusOK, w.Code)
}
// Test not found
router.Use(func(c *Context) {
// For not found routes full path is empty
assert.Empty(t, c.FullPath())
})
w := PerformRequest(router, http.MethodGet, "/not-found")
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) {
r := New()
r.HandleMethodNotAllowed = true
base := r.Group("base")
base.GET("/metrics", handlerTest1)
v1 := base.Group("v1")
v1.GET("/:id/devices", handlerTest1)
v1.GET("/user/:id/groups", handlerTest1)
v1.GET("/orgs/:id", handlerTest1)
v1.DELETE("/orgs/:id", handlerTest1)
w := PerformRequest(r, http.MethodGet, "/base/v1/user/groups")
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouterGoogleAIPCustomVerbRoutes(t *testing.T) {
router := New()
router.POST("/users:batchGet", func(c *Context) {
assert.Equal(t, "/users:batchGet", c.FullPath())
c.String(http.StatusOK, "batch-get")
})
router.POST("/users:go", func(c *Context) {
assert.Equal(t, "/users:go", c.FullPath())
assert.Empty(t, c.Param("go"))
c.String(http.StatusOK, "go")
})
router.POST("/users:batchCreate", func(c *Context) {
assert.Equal(t, "/users:batchCreate", c.FullPath())
c.String(http.StatusOK, "batch-create")
})
w := PerformRequest(router, http.MethodPost, "/users:batchGet")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "batch-get", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/users:batchCreate")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "batch-create", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/users:go")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "go", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/users:anything")
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouterGoogleAIPCustomVerbAfterParam(t *testing.T) {
router := New()
router.POST("/customers/:customer_id", func(c *Context) {
assert.Equal(t, "/customers/:customer_id", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "get")
})
router.POST("/customers/:customer_id:mutate", func(c *Context) {
assert.Equal(t, "/customers/:customer_id:mutate", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "mutate")
})
router.POST("/customers/:customer_id:mutate/static", func(c *Context) {
assert.Equal(t, "/customers/:customer_id:mutate/static", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "static")
})
router.POST("/customers/:customer_id:mutate/:name", func(c *Context) {
assert.Equal(t, "/customers/:customer_id:mutate/:name", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, c.Param("name"))
})
router.POST("/customers/:customer_id/devices", func(c *Context) {
assert.Equal(t, "/customers/:customer_id/devices", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "devices")
})
w := PerformRequest(router, http.MethodPost, "/customers/123")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "get", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123:mutate")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "mutate", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123:mutate/static")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "static", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123:mutate/details")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "details", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123/devices")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "devices", w.Body.String())
}
func TestRouterPrefixParamRoutesRemainSupported(t *testing.T) {
router := New()
router.GET("/id:id", func(c *Context) {
assert.Equal(t, "/id:id", c.FullPath())
c.String(http.StatusOK, c.Param("id"))
})
router.GET("/v:version", func(c *Context) {
assert.Equal(t, "/v:version", c.FullPath())
c.String(http.StatusOK, c.Param("version"))
})
w := PerformRequest(router, http.MethodGet, "/id123")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "123", w.Body.String())
w = PerformRequest(router, http.MethodGet, "/v1")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "1", w.Body.String())
}