mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 22:53:34 +08:00
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.
896 lines
28 KiB
Go
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())
|
|
}
|