test(router): cover AIP custom verb routes

Custom verb support needs coverage beyond the tree insertion changes.

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

The assertions cover handler selection, FullPath, Param values, and parameter counting.
This commit is contained in:
Kuroda Kayn 2026-06-28 15:21:27 +08:00
parent ddf82d7611
commit 6bc76c3404
2 changed files with 165 additions and 5 deletions

View File

@ -789,3 +789,107 @@ func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) {
w := PerformRequest(r, http.MethodGet, "/base/v1/user/groups")
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouterGoogleAIPCustomVerbRoutes(t *testing.T) {
router := New()
router.POST("/users:batchGet", func(c *Context) {
assert.Equal(t, "/users:batchGet", c.FullPath())
c.String(http.StatusOK, "batch-get")
})
router.POST("/users:go", func(c *Context) {
assert.Equal(t, "/users:go", c.FullPath())
assert.Empty(t, c.Param("go"))
c.String(http.StatusOK, "go")
})
router.POST("/users:batchCreate", func(c *Context) {
assert.Equal(t, "/users:batchCreate", c.FullPath())
c.String(http.StatusOK, "batch-create")
})
w := PerformRequest(router, http.MethodPost, "/users:batchGet")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "batch-get", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/users:batchCreate")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "batch-create", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/users:go")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "go", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/users:anything")
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouterGoogleAIPCustomVerbAfterParam(t *testing.T) {
router := New()
router.POST("/customers/:customer_id", func(c *Context) {
assert.Equal(t, "/customers/:customer_id", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "get")
})
router.POST("/customers/:customer_id:mutate", func(c *Context) {
assert.Equal(t, "/customers/:customer_id:mutate", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "mutate")
})
router.POST("/customers/:customer_id:mutate/static", func(c *Context) {
assert.Equal(t, "/customers/:customer_id:mutate/static", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "static")
})
router.POST("/customers/:customer_id:mutate/:name", func(c *Context) {
assert.Equal(t, "/customers/:customer_id:mutate/:name", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, c.Param("name"))
})
router.POST("/customers/:customer_id/devices", func(c *Context) {
assert.Equal(t, "/customers/:customer_id/devices", c.FullPath())
assert.Equal(t, "123", c.Param("customer_id"))
c.String(http.StatusOK, "devices")
})
w := PerformRequest(router, http.MethodPost, "/customers/123")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "get", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123:mutate")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "mutate", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123:mutate/static")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "static", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123:mutate/details")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "details", w.Body.String())
w = PerformRequest(router, http.MethodPost, "/customers/123/devices")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "devices", w.Body.String())
}
func TestRouterPrefixParamRoutesRemainSupported(t *testing.T) {
router := New()
router.GET("/id:id", func(c *Context) {
assert.Equal(t, "/id:id", c.FullPath())
c.String(http.StatusOK, c.Param("id"))
})
router.GET("/v:version", func(c *Context) {
assert.Equal(t, "/v:version", c.FullPath())
c.String(http.StatusOK, c.Param("version"))
})
w := PerformRequest(router, http.MethodGet, "/id123")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "123", w.Body.String())
w = PerformRequest(router, http.MethodGet, "/v1")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "1", w.Body.String())
}

View File

@ -93,6 +93,18 @@ func TestCountParams(t *testing.T) {
if countParams("/path/:param1/static/*catch-all") != 2 {
t.Fail()
}
if countParams("/users:batchGet") != 0 {
t.Fail()
}
if countParams("/users:go") != 0 {
t.Fail()
}
if countParams("/v:version") != 1 {
t.Fail()
}
if countParams("/customers/:customer_id:mutate") != 1 {
t.Fail()
}
if countParams(strings.Repeat("/:param", 256)) != 256 {
t.Fail()
}
@ -496,8 +508,6 @@ func TestEmptyWildcardName(t *testing.T) {
tree := &node{}
routes := [...]string{
"/user:",
"/user:/",
"/cmd/:/",
"/src/*",
}
@ -540,9 +550,9 @@ func TestTreeDoubleWildcard(t *testing.T) {
const panicMsg = "only one wildcard per path segment is allowed"
routes := [...]string{
"/:foo:bar",
"/:foo:bar/",
"/:foo*bar",
"/:foo*bar:baz",
"/*foo:bar",
}
for _, route := range routes {
@ -557,6 +567,49 @@ func TestTreeDoubleWildcard(t *testing.T) {
}
}
func TestTreeCustomVerb(t *testing.T) {
tree := &node{}
routes := [...]string{
"/user:",
"/user:/",
"/users:batchGet",
"/users:batchCreate",
"/customers/:customer_id",
"/customers/:customer_id:mutate",
}
for _, route := range routes {
tree.addRoute(route, fakeHandler(route))
}
checkRequests(t, tree, testRequests{
{"/user:", false, "/user:", nil},
{"/user:/", false, "/user:/", nil},
{"/users:batchGet", false, "/users:batchGet", nil},
{"/users:batchCreate", false, "/users:batchCreate", nil},
{"/customers/123", false, "/customers/:customer_id", Params{Param{Key: "customer_id", Value: "123"}}},
{"/customers/123:mutate", false, "/customers/:customer_id:mutate", Params{Param{Key: "customer_id", Value: "123"}}},
{"/customers/123:mutatex", false, "/customers/:customer_id", Params{Param{Key: "customer_id", Value: "123:mutatex"}}},
})
}
func TestTreeCustomVerbBeforeBaseParam(t *testing.T) {
tree := &node{}
routes := [...]string{
"/customers/:customer_id:mutate",
"/customers/:customer_id",
}
for _, route := range routes {
tree.addRoute(route, fakeHandler(route))
}
checkRequests(t, tree, testRequests{
{"/customers/123", false, "/customers/:customer_id", Params{Param{Key: "customer_id", Value: "123"}}},
{"/customers/123:mutate", false, "/customers/:customer_id:mutate", Params{Param{Key: "customer_id", Value: "123"}}},
})
}
/*func TestTreeDuplicateWildcard(t *testing.T) {
tree := &node{}
routes := [...]string{
@ -721,6 +774,8 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
"/hi",
"/b/",
"/ABC/",
"/users:batchGet",
"/customers/:customer_id:mutate",
"/search/:query",
"/cmd/:tool/",
"/src/*filepath",
@ -798,6 +853,8 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
{"/aBc/", "/ABC/", true, false},
{"/abC", "/ABC/", true, true},
{"/abC/", "/ABC/", true, false},
{"/USERS:BATCHGET", "/users:batchGet", true, false},
{"/USERS:BATCHGET/", "/users:batchGet", true, true},
{"/SEARCH/QUERY", "/search/QUERY", true, false},
{"/SEARCH/QUERY/", "/search/QUERY", true, true},
{"/CMD/TOOL/", "/cmd/TOOL/", true, false},
@ -949,7 +1006,6 @@ func TestTreeWildcardConflictEx(t *testing.T) {
{"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
{"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
{"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
{"/con:nection", ":nection", `/con:tact`, `:tact`},
}
for _, conflict := range conflicts {