mirror of
https://github.com/labstack/echo.git
synced 2024-12-22 20:06:21 +02:00
6ef5f77bf2
WIP: make default logger implemented custom writer for jsonlike logs WIP: improve examples WIP: defaultErrorHandler use errors.As to unwrap errors. Update readme WIP: default logger logs json, restore e.Start method WIP: clean router.Match a bit WIP: func types/fields have echo.Context has first element WIP: remove yaml tags as functions etc can not be serialized anyway WIP: change BindPathParams,BindQueryParams,BindHeaders from methods to functions and reverse arguments to be like DefaultBinder.Bind is WIP: improved comments, logger now extracts status from error WIP: go mod tidy WIP: rebase with 4.5.0 WIP: * removed todos. * removed StartAutoTLS and StartH2CServer methods from `StartConfig` * KeyAuth middleware errorhandler can swallow the error and resume next middleware WIP: add RouterConfig.UseEscapedPathForMatching to use escaped path for matching request against routes WIP: FIXMEs WIP: upgrade golang-jwt/jwt to `v4` WIP: refactor http methods to return RouteInfo WIP: refactor static not creating multiple routes WIP: refactor route and middleware adding functions not to return error directly WIP: Use 401 for problematic/missing headers for key auth and JWT middleware (#1552, #1402). > In summary, a 401 Unauthorized response should be used for missing or bad authentication WIP: replace `HTTPError.SetInternal` with `HTTPError.WithInternal` so we could not mutate global error variables WIP: add RouteInfo and RouteMatchType into Context what we could know from in middleware what route was matched and/or type of that match (200/404/405) WIP: make notFoundHandler and methodNotAllowedHandler private. encourage that all errors be handled in Echo.HTTPErrorHandler WIP: server cleanup ideas WIP: routable.ForGroup WIP: note about logger middleware WIP: bind should not default values on second try. use crypto rand for better randomness WIP: router add route as interface and returns info as interface WIP: improve flaky test (remains still flaky) WIP: add notes about bind default values WIP: every route can have their own path params names WIP: routerCreator and different tests WIP: different things WIP: remove route implementation WIP: support custom method types WIP: extractor tests WIP: v5.0.x proposal over v4.4.0
3146 lines
88 KiB
Go
3146 lines
88 KiB
Go
package echo
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/stretchr/testify/assert"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type testRoute struct {
|
|
Method string
|
|
Path string
|
|
Handler string
|
|
}
|
|
|
|
var (
|
|
staticRoutes = []testRoute{
|
|
{"GET", "/", ""},
|
|
{"GET", "/cmd.html", ""},
|
|
{"GET", "/code.html", ""},
|
|
{"GET", "/contrib.html", ""},
|
|
{"GET", "/contribute.html", ""},
|
|
{"GET", "/debugging_with_gdb.html", ""},
|
|
{"GET", "/docs.html", ""},
|
|
{"GET", "/effective_go.html", ""},
|
|
{"GET", "/files.log", ""},
|
|
{"GET", "/gccgo_contribute.html", ""},
|
|
{"GET", "/gccgo_install.html", ""},
|
|
{"GET", "/go-logo-black.png", ""},
|
|
{"GET", "/go-logo-blue.png", ""},
|
|
{"GET", "/go-logo-white.png", ""},
|
|
{"GET", "/go1.1.html", ""},
|
|
{"GET", "/go1.2.html", ""},
|
|
{"GET", "/go1.html", ""},
|
|
{"GET", "/go1compat.html", ""},
|
|
{"GET", "/go_faq.html", ""},
|
|
{"GET", "/go_mem.html", ""},
|
|
{"GET", "/go_spec.html", ""},
|
|
{"GET", "/help.html", ""},
|
|
{"GET", "/ie.css", ""},
|
|
{"GET", "/install-source.html", ""},
|
|
{"GET", "/install.html", ""},
|
|
{"GET", "/logo-153x55.png", ""},
|
|
{"GET", "/Makefile", ""},
|
|
{"GET", "/root.html", ""},
|
|
{"GET", "/share.png", ""},
|
|
{"GET", "/sieve.gif", ""},
|
|
{"GET", "/tos.html", ""},
|
|
{"GET", "/articles/", ""},
|
|
{"GET", "/articles/go_command.html", ""},
|
|
{"GET", "/articles/index.html", ""},
|
|
{"GET", "/articles/wiki/", ""},
|
|
{"GET", "/articles/wiki/edit.html", ""},
|
|
{"GET", "/articles/wiki/final-noclosure.go", ""},
|
|
{"GET", "/articles/wiki/final-noerror.go", ""},
|
|
{"GET", "/articles/wiki/final-parsetemplate.go", ""},
|
|
{"GET", "/articles/wiki/final-template.go", ""},
|
|
{"GET", "/articles/wiki/final.go", ""},
|
|
{"GET", "/articles/wiki/get.go", ""},
|
|
{"GET", "/articles/wiki/http-sample.go", ""},
|
|
{"GET", "/articles/wiki/index.html", ""},
|
|
{"GET", "/articles/wiki/Makefile", ""},
|
|
{"GET", "/articles/wiki/notemplate.go", ""},
|
|
{"GET", "/articles/wiki/part1-noerror.go", ""},
|
|
{"GET", "/articles/wiki/part1.go", ""},
|
|
{"GET", "/articles/wiki/part2.go", ""},
|
|
{"GET", "/articles/wiki/part3-errorhandling.go", ""},
|
|
{"GET", "/articles/wiki/part3.go", ""},
|
|
{"GET", "/articles/wiki/test.bash", ""},
|
|
{"GET", "/articles/wiki/test_edit.good", ""},
|
|
{"GET", "/articles/wiki/test_Test.txt.good", ""},
|
|
{"GET", "/articles/wiki/test_view.good", ""},
|
|
{"GET", "/articles/wiki/view.html", ""},
|
|
{"GET", "/codewalk/", ""},
|
|
{"GET", "/codewalk/codewalk.css", ""},
|
|
{"GET", "/codewalk/codewalk.js", ""},
|
|
{"GET", "/codewalk/codewalk.xml", ""},
|
|
{"GET", "/codewalk/functions.xml", ""},
|
|
{"GET", "/codewalk/markov.go", ""},
|
|
{"GET", "/codewalk/markov.xml", ""},
|
|
{"GET", "/codewalk/pig.go", ""},
|
|
{"GET", "/codewalk/popout.png", ""},
|
|
{"GET", "/codewalk/run", ""},
|
|
{"GET", "/codewalk/sharemem.xml", ""},
|
|
{"GET", "/codewalk/urlpoll.go", ""},
|
|
{"GET", "/devel/", ""},
|
|
{"GET", "/devel/release.html", ""},
|
|
{"GET", "/devel/weekly.html", ""},
|
|
{"GET", "/gopher/", ""},
|
|
{"GET", "/gopher/appenginegopher.jpg", ""},
|
|
{"GET", "/gopher/appenginegophercolor.jpg", ""},
|
|
{"GET", "/gopher/appenginelogo.gif", ""},
|
|
{"GET", "/gopher/bumper.png", ""},
|
|
{"GET", "/gopher/bumper192x108.png", ""},
|
|
{"GET", "/gopher/bumper320x180.png", ""},
|
|
{"GET", "/gopher/bumper480x270.png", ""},
|
|
{"GET", "/gopher/bumper640x360.png", ""},
|
|
{"GET", "/gopher/doc.png", ""},
|
|
{"GET", "/gopher/frontpage.png", ""},
|
|
{"GET", "/gopher/gopherbw.png", ""},
|
|
{"GET", "/gopher/gophercolor.png", ""},
|
|
{"GET", "/gopher/gophercolor16x16.png", ""},
|
|
{"GET", "/gopher/help.png", ""},
|
|
{"GET", "/gopher/pkg.png", ""},
|
|
{"GET", "/gopher/project.png", ""},
|
|
{"GET", "/gopher/ref.png", ""},
|
|
{"GET", "/gopher/run.png", ""},
|
|
{"GET", "/gopher/talks.png", ""},
|
|
{"GET", "/gopher/pencil/", ""},
|
|
{"GET", "/gopher/pencil/gopherhat.jpg", ""},
|
|
{"GET", "/gopher/pencil/gopherhelmet.jpg", ""},
|
|
{"GET", "/gopher/pencil/gophermega.jpg", ""},
|
|
{"GET", "/gopher/pencil/gopherrunning.jpg", ""},
|
|
{"GET", "/gopher/pencil/gopherswim.jpg", ""},
|
|
{"GET", "/gopher/pencil/gopherswrench.jpg", ""},
|
|
{"GET", "/play/", ""},
|
|
{"GET", "/play/fib.go", ""},
|
|
{"GET", "/play/hello.go", ""},
|
|
{"GET", "/play/life.go", ""},
|
|
{"GET", "/play/peano.go", ""},
|
|
{"GET", "/play/pi.go", ""},
|
|
{"GET", "/play/sieve.go", ""},
|
|
{"GET", "/play/solitaire.go", ""},
|
|
{"GET", "/play/tree.go", ""},
|
|
{"GET", "/progs/", ""},
|
|
{"GET", "/progs/cgo1.go", ""},
|
|
{"GET", "/progs/cgo2.go", ""},
|
|
{"GET", "/progs/cgo3.go", ""},
|
|
{"GET", "/progs/cgo4.go", ""},
|
|
{"GET", "/progs/defer.go", ""},
|
|
{"GET", "/progs/defer.out", ""},
|
|
{"GET", "/progs/defer2.go", ""},
|
|
{"GET", "/progs/defer2.out", ""},
|
|
{"GET", "/progs/eff_bytesize.go", ""},
|
|
{"GET", "/progs/eff_bytesize.out", ""},
|
|
{"GET", "/progs/eff_qr.go", ""},
|
|
{"GET", "/progs/eff_sequence.go", ""},
|
|
{"GET", "/progs/eff_sequence.out", ""},
|
|
{"GET", "/progs/eff_unused1.go", ""},
|
|
{"GET", "/progs/eff_unused2.go", ""},
|
|
{"GET", "/progs/error.go", ""},
|
|
{"GET", "/progs/error2.go", ""},
|
|
{"GET", "/progs/error3.go", ""},
|
|
{"GET", "/progs/error4.go", ""},
|
|
{"GET", "/progs/go1.go", ""},
|
|
{"GET", "/progs/gobs1.go", ""},
|
|
{"GET", "/progs/gobs2.go", ""},
|
|
{"GET", "/progs/image_draw.go", ""},
|
|
{"GET", "/progs/image_package1.go", ""},
|
|
{"GET", "/progs/image_package1.out", ""},
|
|
{"GET", "/progs/image_package2.go", ""},
|
|
{"GET", "/progs/image_package2.out", ""},
|
|
{"GET", "/progs/image_package3.go", ""},
|
|
{"GET", "/progs/image_package3.out", ""},
|
|
{"GET", "/progs/image_package4.go", ""},
|
|
{"GET", "/progs/image_package4.out", ""},
|
|
{"GET", "/progs/image_package5.go", ""},
|
|
{"GET", "/progs/image_package5.out", ""},
|
|
{"GET", "/progs/image_package6.go", ""},
|
|
{"GET", "/progs/image_package6.out", ""},
|
|
{"GET", "/progs/interface.go", ""},
|
|
{"GET", "/progs/interface2.go", ""},
|
|
{"GET", "/progs/interface2.out", ""},
|
|
{"GET", "/progs/json1.go", ""},
|
|
{"GET", "/progs/json2.go", ""},
|
|
{"GET", "/progs/json2.out", ""},
|
|
{"GET", "/progs/json3.go", ""},
|
|
{"GET", "/progs/json4.go", ""},
|
|
{"GET", "/progs/json5.go", ""},
|
|
{"GET", "/progs/run", ""},
|
|
{"GET", "/progs/slices.go", ""},
|
|
{"GET", "/progs/timeout1.go", ""},
|
|
{"GET", "/progs/timeout2.go", ""},
|
|
{"GET", "/progs/update.bash", ""},
|
|
}
|
|
|
|
gitHubAPI = []testRoute{
|
|
// OAuth Authorizations
|
|
{"GET", "/authorizations", ""},
|
|
{"GET", "/authorizations/:id", ""},
|
|
{"POST", "/authorizations", ""},
|
|
|
|
{"PUT", "/authorizations/clients/:client_id", ""},
|
|
{"PATCH", "/authorizations/:id", ""},
|
|
|
|
{"DELETE", "/authorizations/:id", ""},
|
|
{"GET", "/applications/:client_id/tokens/:access_token", ""},
|
|
{"DELETE", "/applications/:client_id/tokens", ""},
|
|
{"DELETE", "/applications/:client_id/tokens/:access_token", ""},
|
|
|
|
// Activity
|
|
{"GET", "/events", ""},
|
|
{"GET", "/repos/:owner/:repo/events", ""},
|
|
{"GET", "/networks/:owner/:repo/events", ""},
|
|
{"GET", "/orgs/:org/events", ""},
|
|
{"GET", "/users/:user/received_events", ""},
|
|
{"GET", "/users/:user/received_events/public", ""},
|
|
{"GET", "/users/:user/events", ""},
|
|
{"GET", "/users/:user/events/public", ""},
|
|
{"GET", "/users/:user/events/orgs/:org", ""},
|
|
{"GET", "/feeds", ""},
|
|
{"GET", "/notifications", ""},
|
|
{"GET", "/repos/:owner/:repo/notifications", ""},
|
|
{"PUT", "/notifications", ""},
|
|
{"PUT", "/repos/:owner/:repo/notifications", ""},
|
|
{"GET", "/notifications/threads/:id", ""},
|
|
|
|
{"PATCH", "/notifications/threads/:id", ""},
|
|
|
|
{"GET", "/notifications/threads/:id/subscription", ""},
|
|
{"PUT", "/notifications/threads/:id/subscription", ""},
|
|
{"DELETE", "/notifications/threads/:id/subscription", ""},
|
|
{"GET", "/repos/:owner/:repo/stargazers", ""},
|
|
{"GET", "/users/:user/starred", ""},
|
|
{"GET", "/user/starred", ""},
|
|
{"GET", "/user/starred/:owner/:repo", ""},
|
|
{"PUT", "/user/starred/:owner/:repo", ""},
|
|
{"DELETE", "/user/starred/:owner/:repo", ""},
|
|
{"GET", "/repos/:owner/:repo/subscribers", ""},
|
|
{"GET", "/users/:user/subscriptions", ""},
|
|
{"GET", "/user/subscriptions", ""},
|
|
{"GET", "/repos/:owner/:repo/subscription", ""},
|
|
{"PUT", "/repos/:owner/:repo/subscription", ""},
|
|
{"DELETE", "/repos/:owner/:repo/subscription", ""},
|
|
{"GET", "/user/subscriptions/:owner/:repo", ""},
|
|
{"PUT", "/user/subscriptions/:owner/:repo", ""},
|
|
{"DELETE", "/user/subscriptions/:owner/:repo", ""},
|
|
|
|
// Gists
|
|
{"GET", "/users/:user/gists", ""},
|
|
{"GET", "/gists", ""},
|
|
|
|
{"GET", "/gists/public", ""},
|
|
{"GET", "/gists/starred", ""},
|
|
|
|
{"GET", "/gists/:id", ""},
|
|
{"POST", "/gists", ""},
|
|
|
|
{"PATCH", "/gists/:id", ""},
|
|
|
|
{"PUT", "/gists/:id/star", ""},
|
|
{"DELETE", "/gists/:id/star", ""},
|
|
{"GET", "/gists/:id/star", ""},
|
|
{"POST", "/gists/:id/forks", ""},
|
|
{"DELETE", "/gists/:id", ""},
|
|
|
|
// Git Data
|
|
{"GET", "/repos/:owner/:repo/git/blobs/:sha", ""},
|
|
{"POST", "/repos/:owner/:repo/git/blobs", ""},
|
|
{"GET", "/repos/:owner/:repo/git/commits/:sha", ""},
|
|
{"POST", "/repos/:owner/:repo/git/commits", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/git/refs/*ref", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/git/refs", ""},
|
|
{"POST", "/repos/:owner/:repo/git/refs", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/git/refs/*ref", ""},
|
|
{"DELETE", "/repos/:owner/:repo/git/refs/*ref", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/git/tags/:sha", ""},
|
|
{"POST", "/repos/:owner/:repo/git/tags", ""},
|
|
{"GET", "/repos/:owner/:repo/git/trees/:sha", ""},
|
|
{"POST", "/repos/:owner/:repo/git/trees", ""},
|
|
|
|
// Issues
|
|
{"GET", "/issues", ""},
|
|
{"GET", "/user/issues", ""},
|
|
{"GET", "/orgs/:org/issues", ""},
|
|
{"GET", "/repos/:owner/:repo/issues", ""},
|
|
{"GET", "/repos/:owner/:repo/issues/:number", ""},
|
|
{"POST", "/repos/:owner/:repo/issues", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/issues/:number", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/assignees", ""},
|
|
{"GET", "/repos/:owner/:repo/assignees/:assignee", ""},
|
|
{"GET", "/repos/:owner/:repo/issues/:number/comments", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/issues/comments", ""},
|
|
{"GET", "/repos/:owner/:repo/issues/comments/:id", ""},
|
|
|
|
{"POST", "/repos/:owner/:repo/issues/:number/comments", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/issues/comments/:id", ""},
|
|
{"DELETE", "/repos/:owner/:repo/issues/comments/:id", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/issues/:number/events", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/issues/events", ""},
|
|
{"GET", "/repos/:owner/:repo/issues/events/:id", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/labels", ""},
|
|
{"GET", "/repos/:owner/:repo/labels/:name", ""},
|
|
{"POST", "/repos/:owner/:repo/labels", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/labels/:name", ""},
|
|
|
|
{"DELETE", "/repos/:owner/:repo/labels/:name", ""},
|
|
{"GET", "/repos/:owner/:repo/issues/:number/labels", ""},
|
|
{"POST", "/repos/:owner/:repo/issues/:number/labels", ""},
|
|
{"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name", ""},
|
|
{"PUT", "/repos/:owner/:repo/issues/:number/labels", ""},
|
|
{"DELETE", "/repos/:owner/:repo/issues/:number/labels", ""},
|
|
{"GET", "/repos/:owner/:repo/milestones/:number/labels", ""},
|
|
{"GET", "/repos/:owner/:repo/milestones", ""},
|
|
{"GET", "/repos/:owner/:repo/milestones/:number", ""},
|
|
{"POST", "/repos/:owner/:repo/milestones", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/milestones/:number", ""},
|
|
|
|
{"DELETE", "/repos/:owner/:repo/milestones/:number", ""},
|
|
|
|
// Miscellaneous
|
|
{"GET", "/emojis", ""},
|
|
{"GET", "/gitignore/templates", ""},
|
|
{"GET", "/gitignore/templates/:name", ""},
|
|
{"POST", "/markdown", ""},
|
|
{"POST", "/markdown/raw", ""},
|
|
{"GET", "/meta", ""},
|
|
{"GET", "/rate_limit", ""},
|
|
|
|
// Organizations
|
|
{"GET", "/users/:user/orgs", ""},
|
|
{"GET", "/user/orgs", ""},
|
|
{"GET", "/orgs/:org", ""},
|
|
|
|
{"PATCH", "/orgs/:org", ""},
|
|
|
|
{"GET", "/orgs/:org/members", ""},
|
|
{"GET", "/orgs/:org/members/:user", ""},
|
|
{"DELETE", "/orgs/:org/members/:user", ""},
|
|
{"GET", "/orgs/:org/public_members", ""},
|
|
{"GET", "/orgs/:org/public_members/:user", ""},
|
|
{"PUT", "/orgs/:org/public_members/:user", ""},
|
|
{"DELETE", "/orgs/:org/public_members/:user", ""},
|
|
{"GET", "/orgs/:org/teams", ""},
|
|
{"GET", "/teams/:id", ""},
|
|
{"POST", "/orgs/:org/teams", ""},
|
|
|
|
{"PATCH", "/teams/:id", ""},
|
|
|
|
{"DELETE", "/teams/:id", ""},
|
|
{"GET", "/teams/:id/members", ""},
|
|
{"GET", "/teams/:id/members/:user", ""},
|
|
{"PUT", "/teams/:id/members/:user", ""},
|
|
{"DELETE", "/teams/:id/members/:user", ""},
|
|
{"GET", "/teams/:id/repos", ""},
|
|
{"GET", "/teams/:id/repos/:owner/:repo", ""},
|
|
{"PUT", "/teams/:id/repos/:owner/:repo", ""},
|
|
{"DELETE", "/teams/:id/repos/:owner/:repo", ""},
|
|
{"GET", "/user/teams", ""},
|
|
|
|
// Pull Requests
|
|
{"GET", "/repos/:owner/:repo/pulls", ""},
|
|
{"GET", "/repos/:owner/:repo/pulls/:number", ""},
|
|
{"POST", "/repos/:owner/:repo/pulls", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/pulls/:number", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/pulls/:number/commits", ""},
|
|
{"GET", "/repos/:owner/:repo/pulls/:number/files", ""},
|
|
{"GET", "/repos/:owner/:repo/pulls/:number/merge", ""},
|
|
{"PUT", "/repos/:owner/:repo/pulls/:number/merge", ""},
|
|
{"GET", "/repos/:owner/:repo/pulls/:number/comments", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/pulls/comments", ""},
|
|
{"GET", "/repos/:owner/:repo/pulls/comments/:number", ""},
|
|
|
|
{"PUT", "/repos/:owner/:repo/pulls/:number/comments", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/pulls/comments/:number", ""},
|
|
{"DELETE", "/repos/:owner/:repo/pulls/comments/:number", ""},
|
|
|
|
// Repositories
|
|
{"GET", "/user/repos", ""},
|
|
{"GET", "/users/:user/repos", ""},
|
|
{"GET", "/orgs/:org/repos", ""},
|
|
{"GET", "/repositories", ""},
|
|
{"POST", "/user/repos", ""},
|
|
{"POST", "/orgs/:org/repos", ""},
|
|
{"GET", "/repos/:owner/:repo", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/contributors", ""},
|
|
{"GET", "/repos/:owner/:repo/languages", ""},
|
|
{"GET", "/repos/:owner/:repo/teams", ""},
|
|
{"GET", "/repos/:owner/:repo/tags", ""},
|
|
{"GET", "/repos/:owner/:repo/branches", ""},
|
|
{"GET", "/repos/:owner/:repo/branches/:branch", ""},
|
|
{"DELETE", "/repos/:owner/:repo", ""},
|
|
{"GET", "/repos/:owner/:repo/collaborators", ""},
|
|
{"GET", "/repos/:owner/:repo/collaborators/:user", ""},
|
|
{"PUT", "/repos/:owner/:repo/collaborators/:user", ""},
|
|
{"DELETE", "/repos/:owner/:repo/collaborators/:user", ""},
|
|
{"GET", "/repos/:owner/:repo/comments", ""},
|
|
{"GET", "/repos/:owner/:repo/commits/:sha/comments", ""},
|
|
{"POST", "/repos/:owner/:repo/commits/:sha/comments", ""},
|
|
{"GET", "/repos/:owner/:repo/comments/:id", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/comments/:id", ""},
|
|
|
|
{"DELETE", "/repos/:owner/:repo/comments/:id", ""},
|
|
{"GET", "/repos/:owner/:repo/commits", ""},
|
|
{"GET", "/repos/:owner/:repo/commits/:sha", ""},
|
|
{"GET", "/repos/:owner/:repo/readme", ""},
|
|
|
|
//{"GET", "/repos/:owner/:repo/contents/*path", ""},
|
|
//{"PUT", "/repos/:owner/:repo/contents/*path", ""},
|
|
//{"DELETE", "/repos/:owner/:repo/contents/*path", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/:archive_format/:ref", ""},
|
|
|
|
{"GET", "/repos/:owner/:repo/keys", ""},
|
|
{"GET", "/repos/:owner/:repo/keys/:id", ""},
|
|
{"POST", "/repos/:owner/:repo/keys", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/keys/:id", ""},
|
|
|
|
{"DELETE", "/repos/:owner/:repo/keys/:id", ""},
|
|
{"GET", "/repos/:owner/:repo/downloads", ""},
|
|
{"GET", "/repos/:owner/:repo/downloads/:id", ""},
|
|
{"DELETE", "/repos/:owner/:repo/downloads/:id", ""},
|
|
{"GET", "/repos/:owner/:repo/forks", ""},
|
|
{"POST", "/repos/:owner/:repo/forks", ""},
|
|
{"GET", "/repos/:owner/:repo/hooks", ""},
|
|
{"GET", "/repos/:owner/:repo/hooks/:id", ""},
|
|
{"POST", "/repos/:owner/:repo/hooks", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/hooks/:id", ""},
|
|
|
|
{"POST", "/repos/:owner/:repo/hooks/:id/tests", ""},
|
|
{"DELETE", "/repos/:owner/:repo/hooks/:id", ""},
|
|
{"POST", "/repos/:owner/:repo/merges", ""},
|
|
{"GET", "/repos/:owner/:repo/releases", ""},
|
|
{"GET", "/repos/:owner/:repo/releases/:id", ""},
|
|
{"POST", "/repos/:owner/:repo/releases", ""},
|
|
|
|
{"PATCH", "/repos/:owner/:repo/releases/:id", ""},
|
|
|
|
{"DELETE", "/repos/:owner/:repo/releases/:id", ""},
|
|
{"GET", "/repos/:owner/:repo/releases/:id/assets", ""},
|
|
{"GET", "/repos/:owner/:repo/stats/contributors", ""},
|
|
{"GET", "/repos/:owner/:repo/stats/commit_activity", ""},
|
|
{"GET", "/repos/:owner/:repo/stats/code_frequency", ""},
|
|
{"GET", "/repos/:owner/:repo/stats/participation", ""},
|
|
{"GET", "/repos/:owner/:repo/stats/punch_card", ""},
|
|
{"GET", "/repos/:owner/:repo/statuses/:ref", ""},
|
|
{"POST", "/repos/:owner/:repo/statuses/:ref", ""},
|
|
|
|
// Search
|
|
{"GET", "/search/repositories", ""},
|
|
{"GET", "/search/code", ""},
|
|
{"GET", "/search/issues", ""},
|
|
{"GET", "/search/users", ""},
|
|
{"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword", ""},
|
|
{"GET", "/legacy/repos/search/:keyword", ""},
|
|
{"GET", "/legacy/user/search/:keyword", ""},
|
|
{"GET", "/legacy/user/email/:email", ""},
|
|
|
|
// Users
|
|
{"GET", "/users/:user", ""},
|
|
{"GET", "/user", ""},
|
|
|
|
{"PATCH", "/user", ""},
|
|
|
|
{"GET", "/users", ""},
|
|
{"GET", "/user/emails", ""},
|
|
{"POST", "/user/emails", ""},
|
|
{"DELETE", "/user/emails", ""},
|
|
{"GET", "/users/:user/followers", ""},
|
|
{"GET", "/user/followers", ""},
|
|
{"GET", "/users/:user/following", ""},
|
|
{"GET", "/user/following", ""},
|
|
{"GET", "/user/following/:user", ""},
|
|
{"GET", "/users/:user/following/:target_user", ""},
|
|
{"PUT", "/user/following/:user", ""},
|
|
{"DELETE", "/user/following/:user", ""},
|
|
{"GET", "/users/:user/keys", ""},
|
|
{"GET", "/user/keys", ""},
|
|
{"GET", "/user/keys/:id", ""},
|
|
{"POST", "/user/keys", ""},
|
|
|
|
{"PATCH", "/user/keys/:id", ""},
|
|
|
|
{"DELETE", "/user/keys/:id", ""},
|
|
}
|
|
|
|
parseAPI = []testRoute{
|
|
// Objects
|
|
{"POST", "/1/classes/:className", ""},
|
|
{"GET", "/1/classes/:className/:objectId", ""},
|
|
{"PUT", "/1/classes/:className/:objectId", ""},
|
|
{"GET", "/1/classes/:className", ""},
|
|
{"DELETE", "/1/classes/:className/:objectId", ""},
|
|
|
|
// Users
|
|
{"POST", "/1/users", ""},
|
|
{"GET", "/1/login", ""},
|
|
{"GET", "/1/users/:objectId", ""},
|
|
{"PUT", "/1/users/:objectId", ""},
|
|
{"GET", "/1/users", ""},
|
|
{"DELETE", "/1/users/:objectId", ""},
|
|
{"POST", "/1/requestPasswordReset", ""},
|
|
|
|
// Roles
|
|
{"POST", "/1/roles", ""},
|
|
{"GET", "/1/roles/:objectId", ""},
|
|
{"PUT", "/1/roles/:objectId", ""},
|
|
{"GET", "/1/roles", ""},
|
|
{"DELETE", "/1/roles/:objectId", ""},
|
|
|
|
// Files
|
|
{"POST", "/1/files/:fileName", ""},
|
|
|
|
// Analytics
|
|
{"POST", "/1/events/:eventName", ""},
|
|
|
|
// Push Notifications
|
|
{"POST", "/1/push", ""},
|
|
|
|
// Installations
|
|
{"POST", "/1/installations", ""},
|
|
{"GET", "/1/installations/:objectId", ""},
|
|
{"PUT", "/1/installations/:objectId", ""},
|
|
{"GET", "/1/installations", ""},
|
|
{"DELETE", "/1/installations/:objectId", ""},
|
|
|
|
// Cloud Functions
|
|
{"POST", "/1/functions", ""},
|
|
}
|
|
|
|
googlePlusAPI = []testRoute{
|
|
// People
|
|
{"GET", "/people/:userId", ""},
|
|
{"GET", "/people", ""},
|
|
{"GET", "/activities/:activityId/people/:collection", ""},
|
|
{"GET", "/people/:userId/people/:collection", ""},
|
|
{"GET", "/people/:userId/openIdConnect", ""},
|
|
|
|
// Activities
|
|
{"GET", "/people/:userId/activities/:collection", ""},
|
|
{"GET", "/activities/:activityId", ""},
|
|
{"GET", "/activities", ""},
|
|
|
|
// Comments
|
|
{"GET", "/activities/:activityId/comments", ""},
|
|
{"GET", "/comments/:commentId", ""},
|
|
|
|
// Moments
|
|
{"POST", "/people/:userId/moments/:collection", ""},
|
|
{"GET", "/people/:userId/moments/:collection", ""},
|
|
{"DELETE", "/moments/:id", ""},
|
|
}
|
|
|
|
paramAndAnyAPI = []testRoute{
|
|
{"GET", "/root/:first/foo/*", ""},
|
|
{"GET", "/root/:first/:second/*", ""},
|
|
{"GET", "/root/:first/bar/:second/*", ""},
|
|
{"GET", "/root/:first/qux/:second/:third/:fourth", ""},
|
|
{"GET", "/root/:first/qux/:second/:third/:fourth/*", ""},
|
|
{"GET", "/root/*", ""},
|
|
|
|
{"POST", "/root/:first/foo/*", ""},
|
|
{"POST", "/root/:first/:second/*", ""},
|
|
{"POST", "/root/:first/bar/:second/*", ""},
|
|
{"POST", "/root/:first/qux/:second/:third/:fourth", ""},
|
|
{"POST", "/root/:first/qux/:second/:third/:fourth/*", ""},
|
|
{"POST", "/root/*", ""},
|
|
|
|
{"PUT", "/root/:first/foo/*", ""},
|
|
{"PUT", "/root/:first/:second/*", ""},
|
|
{"PUT", "/root/:first/bar/:second/*", ""},
|
|
{"PUT", "/root/:first/qux/:second/:third/:fourth", ""},
|
|
{"PUT", "/root/:first/qux/:second/:third/:fourth/*", ""},
|
|
{"PUT", "/root/*", ""},
|
|
|
|
{"DELETE", "/root/:first/foo/*", ""},
|
|
{"DELETE", "/root/:first/:second/*", ""},
|
|
{"DELETE", "/root/:first/bar/:second/*", ""},
|
|
{"DELETE", "/root/:first/qux/:second/:third/:fourth", ""},
|
|
{"DELETE", "/root/:first/qux/:second/:third/:fourth/*", ""},
|
|
{"DELETE", "/root/*", ""},
|
|
}
|
|
|
|
paramAndAnyAPIToFind = []testRoute{
|
|
{"GET", "/root/one/foo/after/the/asterisk", ""},
|
|
{"GET", "/root/one/foo/path/after/the/asterisk", ""},
|
|
{"GET", "/root/one/two/path/after/the/asterisk", ""},
|
|
{"GET", "/root/one/bar/two/after/the/asterisk", ""},
|
|
{"GET", "/root/one/qux/two/three/four", ""},
|
|
{"GET", "/root/one/qux/two/three/four/after/the/asterisk", ""},
|
|
|
|
{"POST", "/root/one/foo/after/the/asterisk", ""},
|
|
{"POST", "/root/one/foo/path/after/the/asterisk", ""},
|
|
{"POST", "/root/one/two/path/after/the/asterisk", ""},
|
|
{"POST", "/root/one/bar/two/after/the/asterisk", ""},
|
|
{"POST", "/root/one/qux/two/three/four", ""},
|
|
{"POST", "/root/one/qux/two/three/four/after/the/asterisk", ""},
|
|
|
|
{"PUT", "/root/one/foo/after/the/asterisk", ""},
|
|
{"PUT", "/root/one/foo/path/after/the/asterisk", ""},
|
|
{"PUT", "/root/one/two/path/after/the/asterisk", ""},
|
|
{"PUT", "/root/one/bar/two/after/the/asterisk", ""},
|
|
{"PUT", "/root/one/qux/two/three/four", ""},
|
|
{"PUT", "/root/one/qux/two/three/four/after/the/asterisk", ""},
|
|
|
|
{"DELETE", "/root/one/foo/after/the/asterisk", ""},
|
|
{"DELETE", "/root/one/foo/path/after/the/asterisk", ""},
|
|
{"DELETE", "/root/one/two/path/after/the/asterisk", ""},
|
|
{"DELETE", "/root/one/bar/two/after/the/asterisk", ""},
|
|
{"DELETE", "/root/one/qux/two/three/four", ""},
|
|
{"DELETE", "/root/one/qux/two/three/four/after/the/asterisk", ""},
|
|
}
|
|
|
|
missesAPI = []testRoute{
|
|
{"GET", "/missOne", ""},
|
|
{"GET", "/miss/two", ""},
|
|
{"GET", "/miss/three/levels", ""},
|
|
{"GET", "/miss/four/levels/nooo", ""},
|
|
|
|
{"POST", "/missOne", ""},
|
|
{"POST", "/miss/two", ""},
|
|
{"POST", "/miss/three/levels", ""},
|
|
{"POST", "/miss/four/levels/nooo", ""},
|
|
|
|
{"PUT", "/missOne", ""},
|
|
{"PUT", "/miss/two", ""},
|
|
{"PUT", "/miss/three/levels", ""},
|
|
{"PUT", "/miss/four/levels/nooo", ""},
|
|
|
|
{"DELETE", "/missOne", ""},
|
|
{"DELETE", "/miss/two", ""},
|
|
{"DELETE", "/miss/three/levels", ""},
|
|
{"DELETE", "/miss/four/levels/nooo", ""},
|
|
}
|
|
|
|
// handlerHelper created a function that will set a context key for assertion
|
|
handlerHelper = func(key string, value int) func(c Context) error {
|
|
return func(c Context) error {
|
|
c.Set(key, value)
|
|
c.Set("path", c.RouteInfo().Path())
|
|
return nil
|
|
}
|
|
}
|
|
handlerFunc = func(c Context) error {
|
|
c.Set("path", c.RouteInfo().Path())
|
|
return nil
|
|
}
|
|
)
|
|
|
|
func checkUnusedParamValues(t *testing.T, c *context, expectParam map[string]string) {
|
|
for _, p := range c.PathParams() {
|
|
value := p.Value
|
|
if value != "" {
|
|
if expectParam == nil {
|
|
t.Errorf("pValue '%v' is set for param name '%v' but we are not expecting it with expectParam", value, p)
|
|
} else {
|
|
if _, ok := expectParam[p.Name]; !ok {
|
|
t.Errorf("pValue '%v' is set for param name '%v' but we are not expecting it with expectParam", value, p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRouterStatic(t *testing.T) {
|
|
path := "/folders/a/files/echo.gif"
|
|
req := httptest.NewRequest("GET", path, nil)
|
|
|
|
e := New()
|
|
e.GET(path, handlerFunc)
|
|
|
|
pathParams := make(PathParams, 0, 2)
|
|
match := e.router.Match(req, &pathParams)
|
|
|
|
assert.Equal(t, path, match.RoutePath)
|
|
assert.Equal(t, 2, cap(pathParams))
|
|
assert.Len(t, pathParams, 0)
|
|
}
|
|
|
|
func TestRouterParam(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/users/:id", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
name string
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
}{
|
|
{
|
|
name: "route /users/1 to /users/:id",
|
|
whenURL: "/users/1",
|
|
expectRoute: "/users/:id",
|
|
expectParam: map[string]string{"id": "1"},
|
|
},
|
|
{
|
|
name: "route /users/1/ to /users/:id",
|
|
whenURL: "/users/1/",
|
|
expectRoute: "/users/:id",
|
|
expectParam: map[string]string{"id": "1/"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest("GET", tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouter_addAndMatchAllSupportedMethods(t *testing.T) {
|
|
var testCases = []struct {
|
|
name string
|
|
whenMethod string
|
|
}{
|
|
{name: "ok, CONNECT", whenMethod: http.MethodConnect},
|
|
{name: "ok, DELETE", whenMethod: http.MethodDelete},
|
|
{name: "ok, GET", whenMethod: http.MethodGet},
|
|
{name: "ok, HEAD", whenMethod: http.MethodHead},
|
|
{name: "ok, OPTIONS", whenMethod: http.MethodOptions},
|
|
{name: "ok, PATCH", whenMethod: http.MethodPatch},
|
|
{name: "ok, POST", whenMethod: http.MethodPost},
|
|
{name: "ok, PROPFIND", whenMethod: PROPFIND},
|
|
{name: "ok, PUT", whenMethod: http.MethodPut},
|
|
{name: "ok, TRACE", whenMethod: http.MethodTrace},
|
|
{name: "ok, REPORT", whenMethod: REPORT},
|
|
{name: "ok, NON_TRADITIONAL_METHOD", whenMethod: "NON_TRADITIONAL_METHOD"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/*", handlerFunc)
|
|
e.Add(tc.whenMethod, "/my/*", handlerFunc)
|
|
|
|
req := httptest.NewRequest(tc.whenMethod, "/my/some-url", nil)
|
|
rec := httptest.NewRecorder()
|
|
c := e.NewContext(req, rec).(*context)
|
|
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "/my/*", match.RoutePath)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMethodNotAllowedAndNotFound(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
ri, err := e.AddRoute(Route{Method: http.MethodGet, Path: "/*", Handler: handlerFunc})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "GET:/*", ri.Name())
|
|
|
|
ri, err = e.AddRoute(Route{Method: http.MethodPost, Path: "/users/:id", Handler: handlerFunc})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "POST:/users/:id", ri.Name())
|
|
|
|
var testCases = []struct {
|
|
name string
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
name: "exact match for route+method",
|
|
whenMethod: http.MethodPost,
|
|
whenURL: "/users/1",
|
|
expectRoute: "/users/:id",
|
|
expectParam: map[string]string{"id": "1"},
|
|
},
|
|
{
|
|
name: "matches node but not method. sends 405 from best match node",
|
|
whenMethod: http.MethodPut,
|
|
whenURL: "/users/1",
|
|
expectRoute: "/users/:id",
|
|
expectError: ErrMethodNotAllowed,
|
|
},
|
|
{
|
|
name: "best match is any route up in tree",
|
|
whenMethod: http.MethodGet,
|
|
whenURL: "/users/1",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "users/1"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
method := http.MethodGet
|
|
if tc.whenMethod != "" {
|
|
method = tc.whenMethod
|
|
}
|
|
req := httptest.NewRequest(method, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterTwoParam(t *testing.T) {
|
|
e := New()
|
|
e.GET("/users/:uid/files/:fid", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(http.MethodGet, "/users/1/files/1", nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
|
|
assert.Equal(t, "/users/:uid/files/:fid", match.RoutePath)
|
|
assert.Equal(t, "1", c.pathParams.Get("uid", ""))
|
|
assert.Equal(t, "1", c.pathParams.Get("fid", ""))
|
|
}
|
|
|
|
// Issue #378
|
|
func TestRouterParamWithSlash(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/a/:b/c/d/:e", handlerFunc)
|
|
e.GET("/a/:b/c/:d/:f", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
// `2/3` should mapped to path `/a/:b/c/d/:e` and into `:e`
|
|
req := httptest.NewRequest(http.MethodGet, "/a/1/c/d/2/3", nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
assert.Equal(t, "/a/:b/c/d/:e", match.RoutePath)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// Issue #1754 - router needs to backtrack multiple levels upwards in tree to find the matching route
|
|
// route evaluation order
|
|
//
|
|
// Routes:
|
|
// 1) /a/:b/c
|
|
// 2) /a/c/d
|
|
// 3) /a/c/df
|
|
//
|
|
// 4) /a/*/f
|
|
// 5) /:e/c/f
|
|
//
|
|
// 6) /*
|
|
//
|
|
// Searching route for "/a/c/f" should match "/a/*/f"
|
|
// When route `4) /a/*/f` is not added then request for "/a/c/f" should match "/:e/c/f"
|
|
//
|
|
// +----------+
|
|
// +-----+ "/" root +--------------------+--------------------------+
|
|
// | +----------+ | |
|
|
// | | |
|
|
// +-------v-------+ +---v---------+ +-------v---+
|
|
// | "a/" (static) +---------------+ | ":" (param) | | "*" (any) |
|
|
// +-+----------+--+ | +-----------+-+ +-----------+
|
|
// | | | |
|
|
// +---------------v+ +-- ---v------+ +------v----+ +-----v-----------+
|
|
// | "c/d" (static) | | ":" (param) | | "*" (any) | | "/c/f" (static) |
|
|
// +---------+------+ +--------+----+ +----------++ +-----------------+
|
|
// | | |
|
|
// | | |
|
|
// +---------v----+ +------v--------+ +------v--------+
|
|
// | "f" (static) | | "/c" (static) | | "/f" (static) |
|
|
// +--------------+ +---------------+ +---------------+
|
|
func TestRouteMultiLevelBacktracking(t *testing.T) {
|
|
var testCases = []struct {
|
|
name string
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
}{
|
|
{
|
|
name: "route /a/c/df to /a/c/df",
|
|
whenURL: "/a/c/df",
|
|
expectRoute: "/a/c/df",
|
|
},
|
|
{
|
|
name: "route /a/x/df to /a/:b/c",
|
|
whenURL: "/a/x/c",
|
|
expectRoute: "/a/:b/c",
|
|
expectParam: map[string]string{"b": "x"},
|
|
},
|
|
{
|
|
name: "route /a/x/f to /a/*/f",
|
|
whenURL: "/a/x/f",
|
|
expectRoute: "/a/*/f",
|
|
expectParam: map[string]string{"*": "x/f"}, // NOTE: `x` would be probably more suitable
|
|
},
|
|
{
|
|
name: "route /b/c/f to /:e/c/f",
|
|
whenURL: "/b/c/f",
|
|
expectRoute: "/:e/c/f",
|
|
expectParam: map[string]string{"e": "b"},
|
|
},
|
|
{
|
|
name: "route /b/c/c to /*",
|
|
whenURL: "/b/c/c",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "b/c/c"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/a/:b/c", handlerFunc)
|
|
e.GET("/a/c/d", handlerFunc)
|
|
e.GET("/a/c/df", handlerFunc)
|
|
e.GET("/a/*/f", handlerFunc)
|
|
e.GET("/:e/c/f", handlerFunc)
|
|
e.GET("/*", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Issue #1754 - router needs to backtrack multiple levels upwards in tree to find the matching route
|
|
// route evaluation order
|
|
//
|
|
// Request for "/a/c/f" should match "/:e/c/f"
|
|
//
|
|
// +-0,7--------+
|
|
// | "/" (root) |----------------------------------+
|
|
// +------------+ |
|
|
// | | |
|
|
// | | |
|
|
// +-1,6-----------+ | | +-8-----------+ +------v----+
|
|
// | "a/" (static) +<--+ +--------->+ ":" (param) | | "*" (any) |
|
|
// +---------------+ +-------------+ +-----------+
|
|
// | | |
|
|
// +-2--------v-----+ +v-3,5--------+ +-9------v--------+
|
|
// | "c/d" (static) | | ":" (param) | | "/c/f" (static) |
|
|
// +----------------+ +-------------+ +-----------------+
|
|
// |
|
|
// +-4--v----------+
|
|
// | "/c" (static) |
|
|
// +---------------+
|
|
func TestRouteMultiLevelBacktracking2(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/a/:b/c", handlerFunc)
|
|
e.GET("/a/c/d", handlerFunc)
|
|
e.GET("/a/c/df", handlerFunc)
|
|
e.GET("/:e/c/f", handlerFunc)
|
|
e.GET("/*", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
name string
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
}{
|
|
{
|
|
name: "route /a/c/df to /a/c/df",
|
|
whenURL: "/a/c/df",
|
|
expectRoute: "/a/c/df",
|
|
},
|
|
{
|
|
name: "route /a/x/df to /a/:b/c",
|
|
whenURL: "/a/x/c",
|
|
expectRoute: "/a/:b/c",
|
|
expectParam: map[string]string{"b": "x"},
|
|
},
|
|
{
|
|
name: "route /a/c/f to /:e/c/f",
|
|
whenURL: "/a/c/f",
|
|
expectRoute: "/:e/c/f",
|
|
expectParam: map[string]string{"e": "a"},
|
|
},
|
|
{
|
|
name: "route /b/c/f to /:e/c/f",
|
|
whenURL: "/b/c/f",
|
|
expectRoute: "/:e/c/f",
|
|
expectParam: map[string]string{"e": "b"},
|
|
},
|
|
{
|
|
name: "route /b/c/c to /*",
|
|
whenURL: "/b/c/c",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "b/c/c"},
|
|
},
|
|
{ // this traverses `/a/:b/c` and `/:e/c/f` branches and eventually backtracks to `/*`
|
|
name: "route /a/c/cf to /*",
|
|
whenURL: "/a/c/cf",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "a/c/cf"},
|
|
},
|
|
{
|
|
name: "route /anyMatch to /*",
|
|
whenURL: "/anyMatch",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "anyMatch"},
|
|
},
|
|
{
|
|
name: "route /anyMatch/withSlash to /*",
|
|
whenURL: "/anyMatch/withSlash",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "anyMatch/withSlash"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterBacktrackingFromMultipleParamKinds(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/*", handlerFunc) // this can match only path that does not have slash in it
|
|
e.GET("/:1/second", handlerFunc)
|
|
e.GET("/:1/:2", handlerFunc) // this acts as match ANY for all routes that have at least one slash
|
|
e.GET("/:1/:2/third", handlerFunc)
|
|
e.GET("/:1/:2/:3/fourth", handlerFunc)
|
|
e.GET("/:1/:2/:3/:4/fifth", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
var testCases = []struct {
|
|
name string
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
}{
|
|
{
|
|
name: "route /first to /*",
|
|
whenURL: "/first",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "first"},
|
|
},
|
|
{
|
|
name: "route /first/second to /:1/second",
|
|
whenURL: "/first/second",
|
|
expectRoute: "/:1/second",
|
|
expectParam: map[string]string{"1": "first"},
|
|
},
|
|
{
|
|
name: "route /first/second-new to /:1/:2",
|
|
whenURL: "/first/second-new",
|
|
expectRoute: "/:1/:2",
|
|
expectParam: map[string]string{
|
|
"1": "first",
|
|
"2": "second-new",
|
|
},
|
|
},
|
|
{ // FIXME: should match `/:1/:2` when backtracking in tree. this 1 level backtracking fails even with old implementation
|
|
name: "route /first/second/ to /:1/:2",
|
|
whenURL: "/first/second/", /// <-- slash at the end is problematic
|
|
expectRoute: "/*", // "/:1/:2",
|
|
expectParam: map[string]string{"*": "first/second/"}, // map[string]string{"1": "first", "2": "second/"},
|
|
},
|
|
{ // FIXME: should match `/:1/:2`. same backtracking problem. when backtracking is at `/:1/:2` during backtracking this node should be match as it has executable handler
|
|
name: "route /first/second/third/fourth/fifth/nope to /:1/:2",
|
|
whenURL: "/first/second/third/fourth/fifth/nope",
|
|
expectRoute: "/*", // "/:1/:2",
|
|
expectParam: map[string]string{"*": "first/second/third/fourth/fifth/nope"}, // map[string]string{"1": "first", "2": "second/third/fourth/fifth/nope"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Issue #1509
|
|
func TestRouterParamStaticConflict(t *testing.T) {
|
|
e := New()
|
|
|
|
g := e.Group("/g")
|
|
g.GET("/skills", handlerFunc)
|
|
g.GET("/status", handlerFunc)
|
|
g.GET("/:name", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
}{
|
|
{
|
|
whenURL: "/g/s",
|
|
expectRoute: "/g/:name",
|
|
expectParam: map[string]string{"name": "s"},
|
|
},
|
|
{
|
|
whenURL: "/g/status",
|
|
expectRoute: "/g/status",
|
|
expectParam: map[string]string{"name": ""},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterParam_escapeColon(t *testing.T) {
|
|
// to allow Google cloud API like route paths with colon in them
|
|
// i.e. https://service.name/v1/some/resource/name:customVerb <- that `:customVerb` is not path param. It is just a string
|
|
e := New()
|
|
|
|
e.POST("/files/a/long/file\\:undelete", handlerFunc)
|
|
e.POST("/v1/some/resource/name:customVerb", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
expectError string
|
|
}{
|
|
{
|
|
whenURL: "/files/a/long/file\\:undelete",
|
|
expectRoute: "/files/a/long/file\\:undelete",
|
|
expectParam: map[string]string{},
|
|
},
|
|
{
|
|
whenURL: "/files/a/long/file\\:notMatching",
|
|
expectRoute: nil,
|
|
expectError: "code=404, message=Not Found",
|
|
expectParam: nil,
|
|
},
|
|
{
|
|
whenURL: "/v1/some/resource/name:PATCH",
|
|
expectRoute: "/v1/some/resource/name:customVerb",
|
|
expectParam: map[string]string{"customVerb": ":PATCH"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
|
|
assert.Equal(t, tc.expectRoute, c.Get("path"))
|
|
if tc.expectError != "" {
|
|
assert.EqualError(t, err, tc.expectError)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.PathParam(param))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterMatchAny(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.GET("/", handlerFunc)
|
|
e.GET("/*", handlerFunc)
|
|
e.GET("/users/*", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
}{
|
|
{
|
|
whenURL: "/",
|
|
expectRoute: "/",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/download",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "download"},
|
|
},
|
|
{
|
|
whenURL: "/users/joe",
|
|
expectRoute: "/users/*",
|
|
expectParam: map[string]string{"*": "joe"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// NOTE: this is to document current implementation. Last added route with `*` asterisk is always the match and no
|
|
// backtracking or more precise matching is done to find more suitable match.
|
|
//
|
|
// Current behaviour might not be correct or expected.
|
|
// But this is where we are without well defined requirements/rules how (multiple) asterisks work in route
|
|
func TestRouterAnyMatchesLastAddedAnyRoute(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/users/*", handlerFunc)
|
|
e.GET("/users/*/action*", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(http.MethodGet, "/users/xxx/action/sea", nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, "/users/*/action*", match.RoutePath)
|
|
assert.Equal(t, "xxx/action/sea", c.pathParams.Get("*", ""))
|
|
|
|
// if we add another route then it is the last added and so it is matched
|
|
e.GET("/users/*/action/search", handlerFunc)
|
|
|
|
c2 := e.NewContext(nil, nil).(*context)
|
|
req2 := httptest.NewRequest(http.MethodGet, "/users/xxx/action/sea", nil)
|
|
match2 := e.router.Match(req2, c2.pathParams)
|
|
c2.route = match2.RouteInfo
|
|
|
|
assert.NoError(t, match2.Handler(c2))
|
|
assert.Equal(t, "/users/*/action/search", match2.RoutePath)
|
|
assert.Equal(t, "xxx/action/sea", c2.pathParams.Get("*", ""))
|
|
}
|
|
|
|
// Issue #1739
|
|
func TestRouterMatchAnyPrefixIssue(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.GET("/*", handlerFunc)
|
|
e.GET("/users/*", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
}{
|
|
{
|
|
whenURL: "/",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/users",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "users"},
|
|
},
|
|
{
|
|
whenURL: "/users/",
|
|
expectRoute: "/users/*",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/users_prefix",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "users_prefix"},
|
|
},
|
|
{
|
|
whenURL: "/users_prefix/",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "users_prefix/"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRouterMatchAnySlash shall verify finding the best route
|
|
// for any routes with trailing slash requests
|
|
func TestRouterMatchAnySlash(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.GET("/users", handlerFunc)
|
|
e.GET("/users/*", handlerFunc)
|
|
e.GET("/img/*", handlerFunc)
|
|
e.GET("/img/load", handlerFunc)
|
|
e.GET("/img/load/*", handlerFunc)
|
|
e.GET("/assets/*", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/",
|
|
expectRoute: "",
|
|
expectParam: map[string]string{"*": ""},
|
|
expectError: ErrNotFound,
|
|
},
|
|
{ // Test trailing slash request for simple any route (see #1526)
|
|
whenURL: "/users/",
|
|
expectRoute: "/users/*",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/users/joe",
|
|
expectRoute: "/users/*",
|
|
expectParam: map[string]string{"*": "joe"},
|
|
},
|
|
// Test trailing slash request for nested any route (see #1526)
|
|
{
|
|
whenURL: "/img/load",
|
|
expectRoute: "/img/load",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/img/load/",
|
|
expectRoute: "/img/load/*",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/img/load/ben",
|
|
expectRoute: "/img/load/*",
|
|
expectParam: map[string]string{"*": "ben"},
|
|
},
|
|
// Test /assets/* any route
|
|
{ // ... without trailing slash must not match
|
|
whenURL: "/assets",
|
|
expectRoute: "",
|
|
expectParam: map[string]string{"*": ""},
|
|
expectError: ErrNotFound,
|
|
},
|
|
|
|
{ // ... with trailing slash must match
|
|
whenURL: "/assets/",
|
|
expectRoute: "/assets/*",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterMatchAnyMultiLevel(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.GET("/api/users/jack", handlerFunc)
|
|
e.GET("/api/users/jill", handlerFunc)
|
|
e.GET("/api/users/*", handlerFunc)
|
|
e.GET("/api/*", handlerFunc)
|
|
e.GET("/other/*", handlerFunc)
|
|
e.GET("/*", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/api/users/jack",
|
|
expectRoute: "/api/users/jack",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/api/users/jill",
|
|
expectRoute: "/api/users/jill",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/api/users/joe",
|
|
expectRoute: "/api/users/*",
|
|
expectParam: map[string]string{"*": "joe"},
|
|
},
|
|
{
|
|
whenURL: "/api/nousers/joe",
|
|
expectRoute: "/api/*",
|
|
expectParam: map[string]string{"*": "nousers/joe"},
|
|
},
|
|
{
|
|
whenURL: "/api/none",
|
|
expectRoute: "/api/*",
|
|
expectParam: map[string]string{"*": "none"},
|
|
},
|
|
{
|
|
whenURL: "/api/none",
|
|
expectRoute: "/api/*",
|
|
expectParam: map[string]string{"*": "none"},
|
|
},
|
|
{
|
|
whenURL: "/noapi/users/jim",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "noapi/users/jim"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
func TestRouterMatchAnyMultiLevelWithPost(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.POST("/api/auth/login", handlerFunc)
|
|
e.POST("/api/auth/forgotPassword", handlerFunc)
|
|
e.Any("/api/*", handlerFunc)
|
|
e.Any("/*", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{ // POST /api/auth/login shall choose login method
|
|
whenURL: "/api/auth/login",
|
|
whenMethod: http.MethodPost,
|
|
expectRoute: "/api/auth/login",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{ // POST /api/auth/logout shall choose nearest any route
|
|
whenURL: "/api/auth/logout",
|
|
whenMethod: http.MethodPost,
|
|
expectRoute: "/api/*",
|
|
expectParam: map[string]string{"*": "auth/logout"},
|
|
},
|
|
{ // POST to /api/other/test shall choose nearest any route
|
|
whenURL: "/api/other/test",
|
|
whenMethod: http.MethodPost,
|
|
expectRoute: "/api/*",
|
|
expectParam: map[string]string{"*": "other/test"},
|
|
},
|
|
{ // GET to /api/other/test shall choose nearest any route
|
|
whenURL: "/api/other/test",
|
|
whenMethod: http.MethodGet,
|
|
expectRoute: "/api/*",
|
|
expectParam: map[string]string{"*": "other/test"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
method := http.MethodGet
|
|
if tc.whenMethod != "" {
|
|
method = tc.whenMethod
|
|
}
|
|
|
|
req := httptest.NewRequest(method, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterMicroParam(t *testing.T) {
|
|
e := New()
|
|
e.GET("/:a/:b/:c", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(http.MethodGet, "/1/2/3", nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, "1", c.pathParams.Get("a", "---none---"))
|
|
assert.Equal(t, "2", c.pathParams.Get("b", "---none---"))
|
|
assert.Equal(t, "3", c.pathParams.Get("c", "---none---"))
|
|
}
|
|
|
|
func TestRouterMixParamMatchAny(t *testing.T) {
|
|
e := New()
|
|
|
|
// Route
|
|
e.GET("/users/:id/*", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(http.MethodGet, "/users/joe/comments", nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, "/users/:id/*", match.RoutePath)
|
|
assert.Equal(t, "joe", c.pathParams.Get("id", "---none---"))
|
|
assert.Equal(t, "comments", c.pathParams.Get("*", "---none---"))
|
|
}
|
|
|
|
func TestRouterMultiRoute(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.GET("/users", handlerFunc)
|
|
e.GET("/users/:id", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/users",
|
|
expectRoute: "/users",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/users/1",
|
|
expectRoute: "/users/:id",
|
|
expectParam: map[string]string{"id": "1"},
|
|
},
|
|
{
|
|
whenURL: "/user",
|
|
expectRoute: "",
|
|
expectParam: map[string]string{"*": ""},
|
|
expectError: ErrNotFound,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterPriority(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.GET("/users", handlerFunc)
|
|
e.GET("/users/new", handlerFunc)
|
|
e.GET("/users/:id", handlerFunc)
|
|
e.GET("/users/dew", handlerFunc)
|
|
e.GET("/users/:id/files", handlerFunc)
|
|
e.GET("/users/newsee", handlerFunc)
|
|
e.GET("/users/*", handlerFunc)
|
|
e.GET("/users/new/*", handlerFunc)
|
|
e.GET("/*", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/users",
|
|
expectRoute: "/users",
|
|
},
|
|
{
|
|
whenURL: "/users/new",
|
|
expectRoute: "/users/new",
|
|
},
|
|
{
|
|
whenURL: "/users/1",
|
|
expectRoute: "/users/:id",
|
|
expectParam: map[string]string{"id": "1"},
|
|
},
|
|
{
|
|
whenURL: "/users/dew",
|
|
expectRoute: "/users/dew",
|
|
},
|
|
{
|
|
whenURL: "/users/1/files",
|
|
expectRoute: "/users/:id/files",
|
|
expectParam: map[string]string{"id": "1"},
|
|
},
|
|
{
|
|
whenURL: "/users/new",
|
|
expectRoute: "/users/new",
|
|
},
|
|
{
|
|
whenURL: "/users/news",
|
|
expectRoute: "/users/:id",
|
|
expectParam: map[string]string{"id": "news"},
|
|
},
|
|
{
|
|
whenURL: "/users/newsee",
|
|
expectRoute: "/users/newsee",
|
|
},
|
|
{
|
|
whenURL: "/users/joe/books",
|
|
expectRoute: "/users/*",
|
|
expectParam: map[string]string{"*": "joe/books"},
|
|
},
|
|
{
|
|
whenURL: "/users/new/someone",
|
|
expectRoute: "/users/new/*",
|
|
expectParam: map[string]string{"*": "someone"},
|
|
},
|
|
{
|
|
whenURL: "/users/dew/someone",
|
|
expectRoute: "/users/*",
|
|
expectParam: map[string]string{"*": "dew/someone"},
|
|
},
|
|
{ // Route > /users/* should be matched although /users/dew exists
|
|
whenURL: "/users/notexists/someone",
|
|
expectRoute: "/users/*",
|
|
expectParam: map[string]string{"*": "notexists/someone"},
|
|
},
|
|
{
|
|
whenURL: "/nousers",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "nousers"},
|
|
},
|
|
{
|
|
whenURL: "/nousers/new",
|
|
expectRoute: "/*",
|
|
expectParam: map[string]string{"*": "nousers/new"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
method := http.MethodGet
|
|
if tc.whenMethod != "" {
|
|
method = tc.whenMethod
|
|
}
|
|
|
|
req := httptest.NewRequest(method, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterIssue1348(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/:lang/", handlerFunc)
|
|
e.GET("/:lang/dupa", handlerFunc)
|
|
}
|
|
|
|
// Issue #372
|
|
func TestRouterPriorityNotFound(t *testing.T) {
|
|
e := New()
|
|
|
|
// Add
|
|
e.GET("/a/foo", handlerFunc)
|
|
e.GET("/a/bar", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/a/foo",
|
|
expectRoute: "/a/foo",
|
|
},
|
|
{
|
|
whenURL: "/a/bar",
|
|
expectRoute: "/a/bar",
|
|
},
|
|
{
|
|
whenURL: "/abc/def",
|
|
expectRoute: "",
|
|
expectError: ErrNotFound,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
method := http.MethodGet
|
|
if tc.whenMethod != "" {
|
|
method = tc.whenMethod
|
|
}
|
|
|
|
req := httptest.NewRequest(method, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterParamNames(t *testing.T) {
|
|
e := New()
|
|
|
|
// Routes
|
|
e.GET("/users", handlerFunc)
|
|
e.GET("/users/:id", handlerFunc)
|
|
e.GET("/users/:uid/files/:fid", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute interface{}
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/users",
|
|
expectRoute: "/users",
|
|
},
|
|
{
|
|
whenURL: "/users/1",
|
|
expectRoute: "/users/:id",
|
|
expectParam: map[string]string{"id": "1"},
|
|
},
|
|
{
|
|
whenURL: "/users/1/files/1",
|
|
expectRoute: "/users/:uid/files/:fid",
|
|
expectParam: map[string]string{
|
|
"uid": "1",
|
|
"fid": "1",
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
method := http.MethodGet
|
|
if tc.whenMethod != "" {
|
|
method = tc.whenMethod
|
|
}
|
|
|
|
req := httptest.NewRequest(method, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Issue #623 and #1406
|
|
func TestRouterStaticDynamicConflict(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/dictionary/skills", handlerFunc)
|
|
e.GET("/dictionary/:name", handlerFunc)
|
|
e.GET("/users/new", handlerFunc)
|
|
e.GET("/users/:name", handlerFunc)
|
|
e.GET("/server", handlerFunc)
|
|
e.GET("/", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/dictionary/skills",
|
|
expectRoute: "/dictionary/skills",
|
|
expectParam: map[string]string{"*": ""},
|
|
},
|
|
{
|
|
whenURL: "/dictionary/skillsnot",
|
|
expectRoute: "/dictionary/:name",
|
|
expectParam: map[string]string{"name": "skillsnot"},
|
|
},
|
|
{
|
|
whenURL: "/dictionary/type",
|
|
expectRoute: "/dictionary/:name",
|
|
expectParam: map[string]string{"name": "type"},
|
|
},
|
|
{
|
|
whenURL: "/server",
|
|
expectRoute: "/server",
|
|
},
|
|
{
|
|
whenURL: "/users/new",
|
|
expectRoute: "/users/new",
|
|
},
|
|
{
|
|
whenURL: "/users/new2",
|
|
expectRoute: "/users/:name",
|
|
expectParam: map[string]string{"name": "new2"},
|
|
},
|
|
{
|
|
whenURL: "/",
|
|
expectRoute: "/",
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
method := http.MethodGet
|
|
if tc.whenMethod != "" {
|
|
method = tc.whenMethod
|
|
}
|
|
|
|
req := httptest.NewRequest(method, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Issue #1348
|
|
func TestRouterParamBacktraceNotFound(t *testing.T) {
|
|
e := New()
|
|
|
|
// Add
|
|
e.GET("/:param1", handlerFunc)
|
|
e.GET("/:param1/foo", handlerFunc)
|
|
e.GET("/:param1/bar", handlerFunc)
|
|
e.GET("/:param1/bar/:param2", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
name string
|
|
whenMethod string
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
name: "route /a to /:param1",
|
|
whenURL: "/a",
|
|
expectRoute: "/:param1",
|
|
expectParam: map[string]string{"param1": "a"},
|
|
},
|
|
{
|
|
name: "route /a/foo to /:param1/foo",
|
|
whenURL: "/a/foo",
|
|
expectRoute: "/:param1/foo",
|
|
expectParam: map[string]string{"param1": "a"},
|
|
},
|
|
{
|
|
name: "route /a/bar to /:param1/bar",
|
|
whenURL: "/a/bar",
|
|
expectRoute: "/:param1/bar",
|
|
expectParam: map[string]string{"param1": "a"},
|
|
},
|
|
{
|
|
name: "route /a/bar/b to /:param1/bar/:param2",
|
|
whenURL: "/a/bar/b",
|
|
expectRoute: "/:param1/bar/:param2",
|
|
expectParam: map[string]string{
|
|
"param1": "a",
|
|
"param2": "b",
|
|
},
|
|
},
|
|
{
|
|
name: "route /a/bbbbb should return 404",
|
|
whenURL: "/a/bbbbb",
|
|
expectRoute: "",
|
|
expectError: ErrNotFound,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
method := http.MethodGet
|
|
if tc.whenMethod != "" {
|
|
method = tc.whenMethod
|
|
}
|
|
|
|
req := httptest.NewRequest(method, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testRouterAPI(t *testing.T, api []testRoute) {
|
|
e := New()
|
|
|
|
for _, route := range api {
|
|
ri, err := e.AddRoute(Route{
|
|
Method: route.Method,
|
|
Path: route.Path,
|
|
Handler: func(c Context) error {
|
|
return nil
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, ri)
|
|
}
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
for _, route := range api {
|
|
t.Run(route.Path, func(t *testing.T) {
|
|
|
|
req := httptest.NewRequest(route.Method, route.Path, nil)
|
|
e.router.Match(req, c.pathParams)
|
|
|
|
tokens := strings.Split(route.Path[1:], "/")
|
|
for _, token := range tokens {
|
|
if token[0] == ':' {
|
|
assert.Equal(t, c.pathParams.Get(token[1:], "---none---"), token)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouterGitHubAPI(t *testing.T) {
|
|
testRouterAPI(t, gitHubAPI)
|
|
}
|
|
|
|
func TestRouter_Match_DifferentParamNamesForSamePlace(t *testing.T) {
|
|
var testCases = []struct {
|
|
name string
|
|
whenURL string
|
|
whenMethod string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
name: "ok, 1=id + 2=file",
|
|
whenURL: "/users/123/file/payroll.csv",
|
|
whenMethod: http.MethodGet,
|
|
expectRoute: "/users/:id/file/:file",
|
|
expectParam: map[string]string{"id": "123", "file": "payroll.csv"},
|
|
},
|
|
{
|
|
name: "ok, 1=id2 + 2=file2",
|
|
whenURL: "/users/123/file/payroll.csv",
|
|
whenMethod: http.MethodPost,
|
|
expectRoute: "/users/:id2/file/:file2",
|
|
expectParam: map[string]string{"id2": "123", "file2": "payroll.csv"},
|
|
},
|
|
{
|
|
name: "ok, 1=uid",
|
|
whenURL: "/users/999/files",
|
|
whenMethod: http.MethodGet,
|
|
expectRoute: "/users/:uid/files",
|
|
expectParam: map[string]string{"uid": "999"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/users/create", handlerFunc)
|
|
e.GET("/users/:id/file/:file", handlerFunc)
|
|
e.POST("/users/:id2/file/:file2", handlerFunc)
|
|
e.GET("/users/:uid/files", handlerFunc)
|
|
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(tc.whenMethod, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Issue #729
|
|
func TestRouterParamAlias(t *testing.T) {
|
|
api := []testRoute{
|
|
{http.MethodGet, "/users/:userID/following", ""},
|
|
{http.MethodGet, "/users/:userID/followedBy", ""},
|
|
{http.MethodGet, "/users/:userID/follow", ""},
|
|
}
|
|
testRouterAPI(t, api)
|
|
}
|
|
|
|
// Issue #1052
|
|
func TestRouterParamOrdering(t *testing.T) {
|
|
api := []testRoute{
|
|
{http.MethodGet, "/:a/:b/:c/:id", ""},
|
|
{http.MethodGet, "/:a/:id", ""},
|
|
{http.MethodGet, "/:a/:e/:id", ""},
|
|
}
|
|
testRouterAPI(t, api)
|
|
api2 := []testRoute{
|
|
{http.MethodGet, "/:a/:id", ""},
|
|
{http.MethodGet, "/:a/:e/:id", ""},
|
|
{http.MethodGet, "/:a/:b/:c/:id", ""},
|
|
}
|
|
testRouterAPI(t, api2)
|
|
api3 := []testRoute{
|
|
{http.MethodGet, "/:a/:b/:c/:id", ""},
|
|
{http.MethodGet, "/:a/:e/:id", ""},
|
|
{http.MethodGet, "/:a/:id", ""},
|
|
}
|
|
testRouterAPI(t, api3)
|
|
}
|
|
|
|
// Issue #1139
|
|
func TestRouterMixedParams(t *testing.T) {
|
|
api := []testRoute{
|
|
{http.MethodGet, "/teacher/:tid/room/suggestions", ""},
|
|
{http.MethodGet, "/teacher/:id", ""},
|
|
}
|
|
testRouterAPI(t, api)
|
|
api2 := []testRoute{
|
|
{http.MethodGet, "/teacher/:id", ""},
|
|
{http.MethodGet, "/teacher/:tid/room/suggestions", ""},
|
|
}
|
|
testRouterAPI(t, api2)
|
|
}
|
|
|
|
// Issue #1466
|
|
func TestRouterParam1466(t *testing.T) {
|
|
e := New()
|
|
|
|
e.POST("/users/signup", handlerFunc)
|
|
e.POST("/users/signup/bulk", handlerFunc)
|
|
e.POST("/users/survey", handlerFunc)
|
|
|
|
e.GET("/users/:username", handlerFunc)
|
|
e.GET("/interests/:name/users", handlerFunc)
|
|
e.GET("/skills/:name/users", handlerFunc)
|
|
// Additional routes for Issue 1479
|
|
e.GET("/users/:username/likes/projects/ids", handlerFunc)
|
|
e.GET("/users/:username/profile", handlerFunc)
|
|
e.GET("/users/:username/uploads/:type", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/users/ajitem",
|
|
expectRoute: "/users/:username",
|
|
expectParam: map[string]string{"username": "ajitem"},
|
|
},
|
|
{
|
|
whenURL: "/users/sharewithme",
|
|
expectRoute: "/users/:username",
|
|
expectParam: map[string]string{"username": "sharewithme"},
|
|
},
|
|
{ // route `/users/signup` is registered for POST. so param route `/users/:username` (lesser priority) is matched as it has GET handler
|
|
whenURL: "/users/signup",
|
|
expectRoute: "/users/:username",
|
|
expectParam: map[string]string{"username": "signup"},
|
|
},
|
|
// Additional assertions for #1479
|
|
{
|
|
whenURL: "/users/sharewithme/likes/projects/ids",
|
|
expectRoute: "/users/:username/likes/projects/ids",
|
|
expectParam: map[string]string{"username": "sharewithme"},
|
|
},
|
|
{
|
|
whenURL: "/users/ajitem/likes/projects/ids",
|
|
expectRoute: "/users/:username/likes/projects/ids",
|
|
expectParam: map[string]string{"username": "ajitem"},
|
|
},
|
|
{
|
|
whenURL: "/users/sharewithme/profile",
|
|
expectRoute: "/users/:username/profile",
|
|
expectParam: map[string]string{"username": "sharewithme"},
|
|
},
|
|
{
|
|
whenURL: "/users/ajitem/profile",
|
|
expectRoute: "/users/:username/profile",
|
|
expectParam: map[string]string{"username": "ajitem"},
|
|
},
|
|
{
|
|
whenURL: "/users/sharewithme/uploads/self",
|
|
expectRoute: "/users/:username/uploads/:type",
|
|
expectParam: map[string]string{
|
|
"username": "sharewithme",
|
|
"type": "self",
|
|
},
|
|
},
|
|
{
|
|
whenURL: "/users/ajitem/uploads/self",
|
|
expectRoute: "/users/:username/uploads/:type",
|
|
expectParam: map[string]string{
|
|
"username": "ajitem",
|
|
"type": "self",
|
|
},
|
|
},
|
|
{
|
|
whenURL: "/users/tree/free",
|
|
expectRoute: "", // not found
|
|
expectParam: map[string]string{"id": ""},
|
|
expectError: ErrNotFound,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, ""))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Issue #1655
|
|
func TestRouterFindNotPanicOrLoopsWhenContextSetParamValuesIsCalledWithLessValuesThanEchoMaxParam(t *testing.T) {
|
|
e := New()
|
|
|
|
v0 := e.Group("/:version")
|
|
v0.GET("/admin", func(c Context) error {
|
|
c.SetPathParams(PathParams{{
|
|
Name: "version",
|
|
Value: "v1",
|
|
}})
|
|
return nil
|
|
})
|
|
|
|
v0.GET("/images/view/:id", handlerHelper("iv", 1))
|
|
v0.GET("/images/:id", handlerHelper("i", 1))
|
|
v0.GET("/view/*", handlerHelper("v", 1))
|
|
|
|
//If this API is called before the next two one panic the other loops ( of course without my fix ;) )
|
|
c := e.NewContext(nil, nil).(*context)
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/admin", nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, "v1", c.PathParam("version"))
|
|
|
|
//panic
|
|
c = e.NewContext(nil, nil).(*context)
|
|
req = httptest.NewRequest(http.MethodGet, "/v1/view/same-data", nil)
|
|
match = e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, "same-data", c.PathParam("*"))
|
|
assert.Equal(t, 1, c.Get("v"))
|
|
|
|
//looping
|
|
c = e.NewContext(nil, nil).(*context)
|
|
req = httptest.NewRequest(http.MethodGet, "/v1/images/view", nil)
|
|
match = e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
assert.NoError(t, match.Handler(c))
|
|
assert.Equal(t, "view", c.PathParam("id"))
|
|
assert.Equal(t, 1, c.Get("i"))
|
|
}
|
|
|
|
// Issue #1653
|
|
func TestRouterPanicWhenParamNoRootOnlyChildsFailsFind(t *testing.T) {
|
|
e := New()
|
|
|
|
e.GET("/users/create", handlerFunc)
|
|
e.GET("/users/:id/edit", handlerFunc)
|
|
e.GET("/users/:id/active", handlerFunc)
|
|
|
|
var testCases = []struct {
|
|
whenURL string
|
|
expectRoute string
|
|
expectParam map[string]string
|
|
expectError error
|
|
}{
|
|
{
|
|
whenURL: "/users/alice/edit",
|
|
expectRoute: "/users/:id/edit",
|
|
expectParam: map[string]string{"id": "alice"},
|
|
},
|
|
{
|
|
whenURL: "/users/bob/active",
|
|
expectRoute: "/users/:id/active",
|
|
expectParam: map[string]string{"id": "bob"},
|
|
},
|
|
{
|
|
whenURL: "/users/create",
|
|
expectRoute: "/users/create",
|
|
expectParam: nil,
|
|
},
|
|
//This panic before the fix for Issue #1653
|
|
{
|
|
whenURL: "/users/createNotFound",
|
|
expectError: ErrNotFound,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.whenURL, func(t *testing.T) {
|
|
c := e.NewContext(nil, nil).(*context)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
match := e.router.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
err := match.Handler(c)
|
|
if tc.expectError != nil {
|
|
assert.Equal(t, tc.expectError, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tc.expectRoute, match.RoutePath)
|
|
for param, expectedValue := range tc.expectParam {
|
|
assert.Equal(t, expectedValue, c.pathParams.Get(param, "---none---"))
|
|
}
|
|
checkUnusedParamValues(t, c, tc.expectParam)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRoutes_ReverseHandlerName(t *testing.T) {
|
|
static := func(Context) error { return nil }
|
|
getUser := func(Context) error { return nil }
|
|
getAny := func(Context) error { return nil }
|
|
getFile := func(Context) error { return nil }
|
|
|
|
var testCases = []struct {
|
|
name string
|
|
when string
|
|
whenArgs []interface{}
|
|
expect string
|
|
expectErr string
|
|
}{
|
|
{
|
|
name: "ok, HandlerName + args",
|
|
when: HandlerName(getFile),
|
|
whenArgs: []interface{}{"1"},
|
|
expect: "/group/users/1/files/:fid",
|
|
},
|
|
{
|
|
name: "ok, HandlerName",
|
|
when: HandlerName(getFile),
|
|
expect: "/group/users/:uid/files/:fid",
|
|
},
|
|
{
|
|
name: "ok, unnamed fixed name",
|
|
when: "GET:/static/file",
|
|
expect: "/static/file",
|
|
},
|
|
{
|
|
name: "ok, unnamed fixed name",
|
|
when: "GET:/users/:id",
|
|
expect: "/users/:id",
|
|
},
|
|
{
|
|
name: "ok, unnamed any route",
|
|
when: "POST:/documents/*",
|
|
expect: "/documents/*",
|
|
},
|
|
{
|
|
name: "ok, unnamed any route + args",
|
|
when: "GET:/documents/*",
|
|
whenArgs: []interface{}{"index.html"},
|
|
expect: "/documents/index.html",
|
|
},
|
|
{
|
|
name: "ok, named route + args",
|
|
when: HandlerName(getFile),
|
|
whenArgs: []interface{}{"1", "abc"},
|
|
expect: "/group/users/1/files/abc",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e := New()
|
|
e.GET("/static/file", static)
|
|
e.GET("/users/:id", getUser)
|
|
e.Any("/documents/*", getAny)
|
|
|
|
g := e.Group("/group")
|
|
g.GET("/roles/:rid/files/:fid", getFile)
|
|
g.AddRoute(Route{Method: http.MethodGet, Path: "/users/:uid/files/:fid", Handler: getFile, Name: HandlerName(getFile)})
|
|
|
|
reversed, err := e.Router().Routes().Reverse(tc.when, tc.whenArgs...)
|
|
|
|
assert.Equal(t, tc.expect, reversed)
|
|
if tc.expectErr != "" {
|
|
assert.EqualError(t, err, tc.expectErr)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRoutes_Reverse(t *testing.T) {
|
|
var testCases = []struct {
|
|
name string
|
|
when string
|
|
whenArgs []interface{}
|
|
expect string
|
|
expectErr string
|
|
}{
|
|
{
|
|
name: "ok, static",
|
|
when: "/static",
|
|
expect: "/static",
|
|
},
|
|
{
|
|
name: "ok, static + args",
|
|
when: "/static",
|
|
whenArgs: []interface{}{"missing param"},
|
|
expect: "/static",
|
|
},
|
|
{
|
|
name: "ok, static/*",
|
|
when: "/static/*",
|
|
expect: "/static/*",
|
|
},
|
|
{
|
|
name: "ok, static/*",
|
|
when: "/static/*",
|
|
whenArgs: []interface{}{"foo.txt"},
|
|
expect: "/static/foo.txt",
|
|
},
|
|
{
|
|
name: "ok, /params/:foo",
|
|
when: "/params/:foo",
|
|
expect: "/params/:foo",
|
|
},
|
|
{
|
|
name: "ok, /params/:foo + args",
|
|
when: "/params/:foo",
|
|
whenArgs: []interface{}{"one"},
|
|
expect: "/params/one",
|
|
},
|
|
{
|
|
name: "ok, /params/:foo/bar/:qux",
|
|
when: "/params/:foo/bar/:qux",
|
|
expect: "/params/:foo/bar/:qux",
|
|
},
|
|
{
|
|
name: "ok, /params/:foo/bar/:qux + args1",
|
|
when: "/params/:foo/bar/:qux",
|
|
whenArgs: []interface{}{"one"},
|
|
expect: "/params/one/bar/:qux",
|
|
},
|
|
{
|
|
name: "ok, /params/:foo/bar/:qux + args2",
|
|
when: "/params/:foo/bar/:qux",
|
|
whenArgs: []interface{}{"one", "two"},
|
|
expect: "/params/one/bar/two",
|
|
},
|
|
{
|
|
name: "ok, /params/:foo/bar/:qux/* + args3",
|
|
when: "/params/:foo/bar/:qux/*",
|
|
whenArgs: []interface{}{"one", "two", "three"},
|
|
expect: "/params/one/bar/two/three",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
dummyHandler := func(Context) error { return nil }
|
|
|
|
router := NewRouter(New(), RouterConfig{})
|
|
router.Add(Route{Path: "/static", Name: "/static", Method: http.MethodGet, Handler: dummyHandler})
|
|
router.Add(Route{Path: "/static/*", Name: "/static/*", Method: http.MethodGet, Handler: dummyHandler})
|
|
router.Add(Route{Path: "/params/:foo", Name: "/params/:foo", Method: http.MethodGet, Handler: dummyHandler})
|
|
router.Add(Route{Path: "/params/:foo/bar/:qux", Name: "/params/:foo/bar/:qux", Method: http.MethodGet, Handler: dummyHandler})
|
|
router.Add(Route{Path: "/params/:foo/bar/:qux/*", Name: "/params/:foo/bar/:qux/*", Method: http.MethodGet, Handler: dummyHandler})
|
|
|
|
reversed, err := router.Routes().Reverse(tc.when, tc.whenArgs...)
|
|
assert.Equal(t, tc.expect, reversed)
|
|
if tc.expectErr != "" {
|
|
assert.EqualError(t, err, tc.expectErr)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouter_Routes(t *testing.T) {
|
|
routes := []*Route{
|
|
{Method: http.MethodGet, Path: "/users/:user/events"},
|
|
{Method: http.MethodGet, Path: "/users/:user/events/public"},
|
|
{Method: http.MethodPost, Path: "/repos/:owner/:repo/git/refs"},
|
|
{Method: http.MethodPost, Path: "/repos/:owner/:repo/git/tags"},
|
|
}
|
|
router := NewRouter(New(), RouterConfig{})
|
|
for _, r := range routes {
|
|
_, err := router.Add(Route{
|
|
Method: r.Method,
|
|
Path: r.Path,
|
|
Handler: func(c Context) error {
|
|
return c.String(http.StatusOK, "OK")
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, len(routes), len(router.Routes()))
|
|
for _, r := range router.Routes() {
|
|
found := false
|
|
for _, rr := range routes {
|
|
if r.Method() == rr.Method && r.Path() == rr.Path {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Route %s %s not found", r.Method(), r.Path())
|
|
}
|
|
}
|
|
}
|
|
|
|
func benchmarkRouterRoutes(b *testing.B, routes []testRoute, routesToFind []testRoute) {
|
|
e := New()
|
|
r := e.router
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
b.ReportAllocs()
|
|
|
|
// Add routes
|
|
for _, route := range routes {
|
|
e.AddRoute(Route{
|
|
Method: route.Method,
|
|
Path: route.Path,
|
|
Handler: func(c Context) error {
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
|
|
// Routes adding are performed just once, so it doesn't make sense to see that in the benchmark
|
|
b.ResetTimer()
|
|
|
|
// Find routes
|
|
for i := 0; i < b.N; i++ {
|
|
for _, route := range routesToFind {
|
|
c := e.contextPool.Get().(*context)
|
|
|
|
req.Method = route.Method
|
|
req.URL.Path = route.Path
|
|
|
|
match := r.Match(req, c.pathParams)
|
|
c.route = match.RouteInfo
|
|
|
|
e.contextPool.Put(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDefaultRouter_Remove(t *testing.T) {
|
|
var testCases = []struct {
|
|
name string
|
|
givenPaths []string
|
|
whenMethod string
|
|
whenPath string
|
|
expectErr string
|
|
}{
|
|
{
|
|
name: "ok, static",
|
|
whenPath: "/users",
|
|
},
|
|
{
|
|
name: "ok, static without slash",
|
|
whenPath: "users",
|
|
},
|
|
{
|
|
name: "ok, multilevel static",
|
|
whenPath: "/users/newsee",
|
|
},
|
|
{
|
|
name: "ok, static with path params",
|
|
whenPath: "/users/:id/files",
|
|
},
|
|
{
|
|
name: "ok, path params",
|
|
whenPath: "/users/:id",
|
|
},
|
|
{
|
|
name: "ok, any",
|
|
whenPath: "/users/new/*",
|
|
},
|
|
{
|
|
name: "ok, any root",
|
|
whenPath: "/*",
|
|
},
|
|
{
|
|
name: "ok, multilevel any",
|
|
whenPath: "/users/dew/*",
|
|
},
|
|
{
|
|
name: "ok, single root",
|
|
givenPaths: []string{"/users"},
|
|
whenPath: "/users",
|
|
},
|
|
{
|
|
name: "nok, no routes, nothing to remove",
|
|
givenPaths: []string{},
|
|
whenPath: "/users/",
|
|
expectErr: "router has no routes to remove",
|
|
},
|
|
{
|
|
name: "nok, route not found (matches partial node)",
|
|
whenPath: "/users/",
|
|
expectErr: "could not find route to remove by given path",
|
|
},
|
|
{
|
|
name: "nok, route not found (matches partial node)",
|
|
whenPath: "",
|
|
expectErr: "could not find route to remove by given path",
|
|
},
|
|
{
|
|
name: "nok, route not found",
|
|
whenPath: "/this_is_not_existent",
|
|
expectErr: "could not find route to remove by given path",
|
|
},
|
|
{
|
|
name: "nok, multilevel static but different method",
|
|
whenPath: "/users/newsee",
|
|
whenMethod: http.MethodPost,
|
|
expectErr: "could not find route to remove by given path and method",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e := New()
|
|
|
|
paths := []string{
|
|
"/users",
|
|
"/users/new",
|
|
"/users/:id",
|
|
"/users/dew",
|
|
"/users/dew/*",
|
|
"/users/:id/files",
|
|
"/users/newsee",
|
|
"/users/new/new",
|
|
"/users/new/*",
|
|
"/*",
|
|
}
|
|
if tc.givenPaths != nil {
|
|
paths = tc.givenPaths
|
|
}
|
|
index := -1
|
|
for i, p := range paths {
|
|
e.GET(p, handlerFunc)
|
|
if p == tc.whenPath {
|
|
index = i
|
|
}
|
|
}
|
|
var toCheckPaths []string
|
|
if index != -1 {
|
|
toCheckPaths = append(paths[:index], paths[index+1:]...)
|
|
}
|
|
|
|
method := tc.whenMethod
|
|
if method == "" {
|
|
method = http.MethodGet
|
|
}
|
|
|
|
err := e.Router().Remove(method, tc.whenPath)
|
|
|
|
if tc.expectErr != "" {
|
|
assert.EqualError(t, err, tc.expectErr)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
for _, p := range toCheckPaths {
|
|
req := httptest.NewRequest(http.MethodGet, p, nil)
|
|
params := make(PathParams, 0, 5)
|
|
match := e.Router().Match(req, ¶ms)
|
|
assert.Equal(t, p, match.RoutePath, "after removing %v we matched wrong route. when matching: %v, got: %v", tc.whenPath, p, match.RoutePath)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultRouter_AddWithoutHandler(t *testing.T) {
|
|
e := New()
|
|
router := NewRouter(e, RouterConfig{})
|
|
|
|
ri, err := router.Add(Route{Method: http.MethodGet, Path: "/info", Handler: nil})
|
|
assert.EqualError(t, err, "GET /info: adding route without handler function")
|
|
assert.Nil(t, ri)
|
|
}
|
|
|
|
func TestDefaultRouter_AddDuplicateRouteNotAllowed(t *testing.T) {
|
|
e := New()
|
|
router := NewRouter(e, RouterConfig{})
|
|
e.router = router
|
|
|
|
ri, err := router.Add(Route{
|
|
Method: http.MethodGet,
|
|
Path: "/info",
|
|
Handler: func(c Context) error {
|
|
return c.String(http.StatusTeapot, "OLD")
|
|
},
|
|
Name: "old",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, ri)
|
|
|
|
ri, err = router.Add(Route{Method: http.MethodGet, Path: "/info", Handler: handlerFunc, Name: "new"})
|
|
assert.Nil(t, ri)
|
|
assert.EqualError(t, err, "GET /info: adding duplicate route (same method+path) is not allowed")
|
|
assert.Len(t, router.Routes(), 1)
|
|
|
|
status, body := request(http.MethodGet, "/info", e)
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
assert.Equal(t, "OLD", body)
|
|
}
|
|
|
|
func TestName(t *testing.T) {
|
|
|
|
}
|
|
|
|
// See issue #1531, #1258 - there are cases when path parameter need to be unescaped
|
|
func TestDefaultRouter_UnescapePathParamValues(t *testing.T) {
|
|
var testCases = []struct {
|
|
name string
|
|
givenUnescapePathParamValues bool
|
|
whenURL string
|
|
expectPath string
|
|
expectPathParams PathParams
|
|
}{
|
|
{
|
|
name: "ok, unescape = true",
|
|
givenUnescapePathParamValues: true,
|
|
whenURL: "/first/value%20with%20space",
|
|
expectPath: "/first/:raw",
|
|
expectPathParams: PathParams{
|
|
{
|
|
Name: "raw",
|
|
Value: "value with space",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "ok, multiple, unescape = true",
|
|
givenUnescapePathParamValues: true,
|
|
whenURL: "/second/%20/with%20space",
|
|
expectPath: "/second/:id/:fileName",
|
|
expectPathParams: PathParams{
|
|
{Name: "id", Value: " "},
|
|
{Name: "fileName", Value: "with space"},
|
|
},
|
|
},
|
|
{
|
|
name: "ok, any route, unescape = true",
|
|
givenUnescapePathParamValues: true,
|
|
whenURL: "/third/%20%2Fwith%20space",
|
|
expectPath: "/third/*",
|
|
expectPathParams: PathParams{
|
|
{Name: "*", Value: " /with space"},
|
|
},
|
|
},
|
|
{
|
|
name: "ok, unescape = false",
|
|
givenUnescapePathParamValues: false,
|
|
whenURL: "/first/value%20with%20space",
|
|
expectPath: "/first/:raw",
|
|
expectPathParams: PathParams{
|
|
{
|
|
Name: "raw",
|
|
Value: "value%20with%20space",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e := New()
|
|
router := NewRouter(e, RouterConfig{UnescapePathParamValues: tc.givenUnescapePathParamValues})
|
|
e.router = router
|
|
|
|
_, err := router.Add(Route{Method: http.MethodGet, Path: "/first/:raw", Handler: handlerFunc})
|
|
assert.NoError(t, err)
|
|
_, err = router.Add(Route{Method: http.MethodGet, Path: "/second/:id/:fileName", Handler: handlerFunc})
|
|
assert.NoError(t, err)
|
|
_, err = router.Add(Route{Method: http.MethodGet, Path: "/third/*", Handler: handlerFunc})
|
|
assert.NoError(t, err)
|
|
|
|
target, _ := url.Parse(tc.whenURL)
|
|
req := httptest.NewRequest(http.MethodGet, target.String(), nil)
|
|
req.URL.RawPath = tc.whenURL
|
|
|
|
params := make(PathParams, 0, 5)
|
|
match := e.Router().Match(req, ¶ms)
|
|
|
|
assert.Equal(t, tc.expectPath, match.RoutePath)
|
|
assert.Equal(t, tc.expectPathParams, params)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultRouter_AddDuplicateRouteAllowed(t *testing.T) {
|
|
e := New()
|
|
router := NewRouter(e, RouterConfig{AllowOverwritingRoute: true})
|
|
e.router = router
|
|
|
|
ri, err := router.Add(Route{Method: http.MethodGet, Path: "/info", Handler: handlerFunc, Name: "old"})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, ri, routeInfo{
|
|
method: http.MethodGet,
|
|
path: "/info",
|
|
name: "old",
|
|
})
|
|
|
|
ri, err = router.Add(Route{
|
|
Method: http.MethodGet,
|
|
Path: "/info",
|
|
Handler: func(c Context) error {
|
|
return c.String(http.StatusTeapot, "NEW")
|
|
},
|
|
Name: "new",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, ri, routeInfo{
|
|
method: http.MethodGet,
|
|
path: "/info",
|
|
name: "new",
|
|
})
|
|
|
|
routes := router.Routes()
|
|
assert.Len(t, routes, 1)
|
|
assert.Equal(t, "new", routes[0].Name())
|
|
|
|
status, body := request(http.MethodGet, "/info", e)
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
assert.Equal(t, "NEW", body)
|
|
}
|
|
|
|
func TestDefaultRouter_UseEscapedPathForRouting(t *testing.T) {
|
|
var testCases = []struct {
|
|
name string
|
|
givenDoNotUseEscapedPathForRouting bool
|
|
whenRawPath string
|
|
expectBody string
|
|
expectStatus int
|
|
}{
|
|
{
|
|
name: "ok, static route",
|
|
whenRawPath: "/what's%20up",
|
|
expectBody: "/what's up",
|
|
expectStatus: http.StatusTeapot,
|
|
},
|
|
{
|
|
name: "nok, rawPath does not match with route path",
|
|
givenDoNotUseEscapedPathForRouting: true,
|
|
whenRawPath: "/what's%20up", // route is "/what's up"
|
|
expectBody: "{\"message\":\"Not Found\"}\n",
|
|
expectStatus: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "ok, match path param",
|
|
whenRawPath: "/test/what's%20up",
|
|
expectBody: "/test/:param|what's up",
|
|
expectStatus: http.StatusTeapot,
|
|
},
|
|
{
|
|
name: "ok, path param is unescaped as it is in rawPath",
|
|
givenDoNotUseEscapedPathForRouting: true,
|
|
whenRawPath: "/test/what's%20up",
|
|
expectBody: "/test/:param|what's%20up",
|
|
expectStatus: http.StatusTeapot,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e := New()
|
|
router := NewRouter(e, RouterConfig{UseEscapedPathForMatching: !tc.givenDoNotUseEscapedPathForRouting})
|
|
e.router = router
|
|
e.contextPathParamAllocSize = 1
|
|
|
|
ri, err := router.Add(Route{
|
|
Method: http.MethodGet,
|
|
Path: "/what's up",
|
|
Handler: func(c Context) error {
|
|
return c.String(http.StatusTeapot, c.RouteInfo().Path())
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, ri)
|
|
ri, err = router.Add(Route{
|
|
Method: http.MethodGet,
|
|
Path: "/test/:param",
|
|
Handler: func(c Context) error {
|
|
return c.String(http.StatusTeapot, c.RouteInfo().Path()+"|"+c.PathParam("param"))
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, ri)
|
|
|
|
status, body := request(http.MethodGet, tc.whenRawPath, e)
|
|
assert.Equal(t, tc.expectStatus, status)
|
|
assert.Equal(t, tc.expectBody, body)
|
|
})
|
|
}
|
|
}
|
|
|
|
type mySimpleRouter struct {
|
|
route Route
|
|
}
|
|
|
|
func (m *mySimpleRouter) Add(addRoute Routable) (RouteInfo, error) {
|
|
route := addRoute.ToRoute()
|
|
h := route.Handler
|
|
route.Handler = func(c Context) error {
|
|
c.Set("router", "mySimpleRouter")
|
|
return h(c)
|
|
}
|
|
m.route = route
|
|
return route.ToRouteInfo([]string{ /* no values */ }), nil
|
|
}
|
|
|
|
func (m *mySimpleRouter) Remove(method string, path string) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mySimpleRouter) Routes() Routes {
|
|
return Routes{m.route.ToRouteInfo(nil)}
|
|
}
|
|
|
|
func (m *mySimpleRouter) Match(req *http.Request, params *PathParams) RouteMatch {
|
|
return RouteMatch{
|
|
RoutePath: m.route.Path,
|
|
Handler: m.route.Handler,
|
|
}
|
|
}
|
|
|
|
func TestCustomRouter_defaultAndVHostRouting(t *testing.T) {
|
|
e := New()
|
|
e.ResetRouterCreator(func(e *Echo) Router {
|
|
return &mySimpleRouter{}
|
|
})
|
|
|
|
e.GET("/info", func(c Context) error {
|
|
return c.String(http.StatusTeapot, "default from "+c.Get("router").(string))
|
|
})
|
|
|
|
g := e.Host("my.vhost.test")
|
|
g.GET("/info", func(c Context) error {
|
|
return c.String(http.StatusTeapot, "my.vhost.test default from "+c.Get("router").(string))
|
|
})
|
|
|
|
// see if default router was our mySimpleRouter
|
|
req := httptest.NewRequest(http.MethodGet, "/info", nil)
|
|
rec := httptest.NewRecorder()
|
|
e.ServeHTTP(rec, req)
|
|
assert.Equal(t, http.StatusTeapot, rec.Code)
|
|
assert.Equal(t, "default from mySimpleRouter", rec.Body.String())
|
|
|
|
// see if vhost router was our mySimpleRouter
|
|
req = httptest.NewRequest(http.MethodGet, "/info", nil)
|
|
req.Host = "my.vhost.test"
|
|
rec = httptest.NewRecorder()
|
|
e.ServeHTTP(rec, req)
|
|
assert.Equal(t, http.StatusTeapot, rec.Code)
|
|
assert.Equal(t, "my.vhost.test default from mySimpleRouter", rec.Body.String())
|
|
}
|
|
|
|
func BenchmarkRouterStaticRoutes(b *testing.B) {
|
|
benchmarkRouterRoutes(b, staticRoutes, staticRoutes)
|
|
}
|
|
|
|
func BenchmarkRouterStaticRoutesMisses(b *testing.B) {
|
|
benchmarkRouterRoutes(b, staticRoutes, missesAPI)
|
|
}
|
|
|
|
func BenchmarkRouterGitHubAPI(b *testing.B) {
|
|
benchmarkRouterRoutes(b, gitHubAPI, gitHubAPI)
|
|
}
|
|
|
|
func BenchmarkRouterGitHubAPIMisses(b *testing.B) {
|
|
benchmarkRouterRoutes(b, gitHubAPI, missesAPI)
|
|
}
|
|
|
|
func BenchmarkRouterParseAPI(b *testing.B) {
|
|
benchmarkRouterRoutes(b, parseAPI, parseAPI)
|
|
}
|
|
|
|
func BenchmarkRouterParseAPIMisses(b *testing.B) {
|
|
benchmarkRouterRoutes(b, parseAPI, missesAPI)
|
|
}
|
|
|
|
func BenchmarkRouterGooglePlusAPI(b *testing.B) {
|
|
benchmarkRouterRoutes(b, googlePlusAPI, googlePlusAPI)
|
|
}
|
|
|
|
func BenchmarkRouterGooglePlusAPIMisses(b *testing.B) {
|
|
benchmarkRouterRoutes(b, googlePlusAPI, missesAPI)
|
|
}
|
|
|
|
func BenchmarkRouterParamsAndAnyAPI(b *testing.B) {
|
|
benchmarkRouterRoutes(b, paramAndAnyAPI, paramAndAnyAPIToFind)
|
|
}
|
|
|
|
func (n *node) printTree(pfx string, tail bool) {
|
|
p := prefix(tail, pfx, "└── ", "├── ")
|
|
fmt.Printf("%s%s, %p: type=%d, parent=%p, handler=%v, paramNames=%v\n", p, n.prefix, n, n.kind, n.parent, n.methods, n.paramsCount)
|
|
|
|
p = prefix(tail, pfx, " ", "│ ")
|
|
|
|
children := n.staticChildren
|
|
l := len(children)
|
|
|
|
if n.paramChild != nil {
|
|
n.paramChild.printTree(p, n.anyChild == nil && l == 0)
|
|
}
|
|
if n.anyChild != nil {
|
|
n.anyChild.printTree(p, l == 0)
|
|
}
|
|
for i := 0; i < l-1; i++ {
|
|
children[i].printTree(p, false)
|
|
}
|
|
if l > 0 {
|
|
children[l-1].printTree(p, true)
|
|
}
|
|
}
|
|
|
|
func prefix(tail bool, p, on, off string) string {
|
|
if tail {
|
|
return fmt.Sprintf("%s%s", p, on)
|
|
}
|
|
return fmt.Sprintf("%s%s", p, off)
|
|
}
|