github.com/gramework/gramework@v1.8.1-0.20231027140105-82555c9057f5/fasthttprouter_tree_test.go (about)

     1  // Copyright 2013 Julien Schmidt. All rights reserved.
     2  // Copyright (c) 2015-2016, 招牌疯子
     3  // Copyright (c) 2017, Kirill Danshin
     4  // Use of this source code is governed by a BSD-style license that can be found
     5  // in the 3rd-Party License/fasthttprouter file.
     6  
     7  package gramework
     8  
     9  import (
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/valyala/fasthttp"
    14  )
    15  
    16  type (
    17  	testRequests []struct {
    18  		path       string
    19  		nilHandler bool
    20  		route      string
    21  		ps         map[string]string
    22  	}
    23  	testRoute struct {
    24  		path     string
    25  		conflict bool
    26  	}
    27  )
    28  
    29  // Used as a workaround since we can't compare functions or their addresses
    30  var fakeHandlerValue string
    31  
    32  // func printChildren(n *node, prefix string) {
    33  // 	fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
    34  // 	for l := len(n.path); l > 0; l-- {
    35  // 		prefix += " "
    36  // 	}
    37  // 	for _, child := range n.children {
    38  // 		printChildren(child, prefix)
    39  // 	}
    40  // }
    41  
    42  func fakeHandler(val string) RequestHandler {
    43  	return func(*Context) {
    44  		fakeHandlerValue = val
    45  	}
    46  }
    47  
    48  func acquireContext(path string) *Context {
    49  	var requestCtx fasthttp.RequestCtx
    50  	var fastRequest fasthttp.Request
    51  	fastRequest.SetRequestURI(path)
    52  	requestCtx.Init(&fastRequest, nil, nil)
    53  	return &Context{
    54  		RequestCtx: &requestCtx,
    55  		Logger:     Logger,
    56  		App:        New(),
    57  	}
    58  }
    59  
    60  func checkRequests(t *testing.T, tree *node, requests testRequests) {
    61  	for _, request := range requests {
    62  		requestCtx := acquireContext(request.path)
    63  		handler, _, _ := tree.GetValue(request.path, requestCtx, "")
    64  
    65  		if handler == nil {
    66  			if !request.nilHandler {
    67  				t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
    68  			}
    69  		} else if request.nilHandler {
    70  			t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
    71  		} else {
    72  			handler(nil)
    73  			if fakeHandlerValue != request.route {
    74  				t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
    75  			}
    76  		}
    77  
    78  		for expectedKey, expectedVal := range request.ps {
    79  			if requestCtx.UserValue(expectedKey) != expectedVal {
    80  				t.Errorf(" mismatch for route '%s'", request.path)
    81  			}
    82  		}
    83  	}
    84  }
    85  
    86  func checkPriorities(t *testing.T, n *node) uint32 {
    87  	var prio uint32
    88  	for i := range n.children {
    89  		prio += checkPriorities(t, n.children[i])
    90  	}
    91  
    92  	if n.handle != nil {
    93  		prio++
    94  	}
    95  
    96  	if n.priority != prio {
    97  		t.Errorf(
    98  			"priority mismatch for node '%s': is %d, should be %d",
    99  			n.path, n.priority, prio,
   100  		)
   101  	}
   102  
   103  	return prio
   104  }
   105  
   106  func checkMaxParams(t *testing.T, n *node) uint8 {
   107  	var maxParams uint8
   108  	for i := range n.children {
   109  		params := checkMaxParams(t, n.children[i])
   110  		if params > maxParams {
   111  			maxParams = params
   112  		}
   113  	}
   114  	if n.nType > root && !n.wildChild {
   115  		maxParams++
   116  	}
   117  
   118  	if n.maxParams != maxParams {
   119  		t.Errorf(
   120  			"maxParams mismatch for node '%s': is %d, should be %d",
   121  			n.path, n.maxParams, maxParams,
   122  		)
   123  	}
   124  
   125  	return maxParams
   126  }
   127  
   128  func TestCountParams(t *testing.T) {
   129  	if countParams("/path/:param1/static/*catch-all") != 2 {
   130  		t.Fail()
   131  	}
   132  	if countParams(strings.Repeat("/:param", 256)) != 255 {
   133  		t.Fail()
   134  	}
   135  }
   136  
   137  func TestTreeAddAndGet(t *testing.T) {
   138  	tree := &node{}
   139  
   140  	routes := [...]string{
   141  		"/hi",
   142  		"/contact",
   143  		"/co",
   144  		"/c",
   145  		"/a",
   146  		"/ab",
   147  		"/doc/",
   148  		"/doc/go_faq.html",
   149  		"/doc/go1.html",
   150  		"/α",
   151  		"/β",
   152  	}
   153  	for _, route := range routes {
   154  		tree.addRoute(route, fakeHandler(route), newRouter(), nil)
   155  	}
   156  
   157  	//printChildren(tree, "")
   158  
   159  	checkRequests(t, tree, testRequests{
   160  		{"/a", false, "/a", nil},
   161  		{"/", true, "", nil},
   162  		{"/hi", false, "/hi", nil},
   163  		{"/contact", false, "/contact", nil},
   164  		{"/co", false, "/co", nil},
   165  		{"/con", true, "", nil},  // key mismatch
   166  		{"/cona", true, "", nil}, // key mismatch
   167  		{"/no", true, "", nil},   // no matching child
   168  		{"/ab", false, "/ab", nil},
   169  		{"/α", false, "/α", nil},
   170  		{"/β", false, "/β", nil},
   171  	})
   172  
   173  	checkPriorities(t, tree)
   174  	checkMaxParams(t, tree)
   175  }
   176  
   177  func TestTreeWildcard(t *testing.T) {
   178  	tree := &node{}
   179  
   180  	routes := [...]string{
   181  		"/",
   182  		"/cmd/:tool/:sub",
   183  		"/cmd/:tool/",
   184  		"/src/*filepath",
   185  		"/search/",
   186  		"/search/:query",
   187  		"/user_:name",
   188  		"/user_:name/about",
   189  		"/files/:dir/*filepath",
   190  		"/doc/",
   191  		"/doc/go_faq.html",
   192  		"/doc/go1.html",
   193  		"/info/:user/public",
   194  		"/info/:user/project/:project",
   195  	}
   196  	for _, route := range routes {
   197  		tree.addRoute(route, fakeHandler(route), newRouter(), nil)
   198  	}
   199  
   200  	//printChildren(tree, "")
   201  
   202  	checkRequests(t, tree, testRequests{
   203  		{"/", false, "/", nil},
   204  		{"/cmd/test/", false, "/cmd/:tool/", map[string]string{"tool": "test"}},
   205  		{"/cmd/test", true, "", map[string]string{"tool": "test"}},
   206  		{"/cmd/test/3", false, "/cmd/:tool/:sub", map[string]string{"tool": "test", "sub": "3"}},
   207  		{"/src/", false, "/src/*filepath", map[string]string{"filepath": "/"}},
   208  		{"/src/some/file.png", false, "/src/*filepath", map[string]string{"filepath": "/some/file.png"}},
   209  		{"/search/", false, "/search/", nil},
   210  		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", map[string]string{"query": "someth!ng+in+ünìcodé"}},
   211  		{"/search/someth!ng+in+ünìcodé/", true, "", map[string]string{"query": "someth!ng+in+ünìcodé"}},
   212  		{"/user_gopher", false, "/user_:name", map[string]string{"name": "gopher"}},
   213  		{"/user_gopher/about", false, "/user_:name/about", map[string]string{"name": "gopher"}},
   214  		{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", map[string]string{"dir": "js", "filepath": "/inc/framework.js"}},
   215  		{"/info/gordon/public", false, "/info/:user/public", map[string]string{"user": "gordon"}},
   216  		{"/info/gordon/project/go", false, "/info/:user/project/:project", map[string]string{"user": "gordon", "project": "go"}},
   217  	})
   218  
   219  	checkPriorities(t, tree)
   220  	checkMaxParams(t, tree)
   221  }
   222  
   223  func catchPanic(testFunc func()) (recv interface{}) {
   224  	defer func() {
   225  		recv = recover()
   226  	}()
   227  
   228  	testFunc()
   229  	return
   230  }
   231  
   232  func testRoutes(t *testing.T, routes []testRoute) {
   233  	tree := &node{}
   234  
   235  	for _, route := range routes {
   236  		recv := catchPanic(func() {
   237  			tree.addRoute(route.path, nil, newRouter(), nil)
   238  		})
   239  
   240  		if route.conflict {
   241  			if recv == nil {
   242  				t.Errorf("no panic for conflicting route '%s'", route.path)
   243  			}
   244  		} else if recv != nil {
   245  			t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
   246  		}
   247  	}
   248  
   249  	//printChildren(tree, "")
   250  }
   251  
   252  func TestTreeWildcardConflict(t *testing.T) {
   253  	routes := []testRoute{
   254  		{"/cmd/:tool/:sub", false},
   255  		{"/cmd/vet", true},
   256  		{"/src/*filepath", false},
   257  		{"/src/*filepathx", true},
   258  		{"/src/", true},
   259  		{"/src1/", false},
   260  		{"/src1/*filepath", true},
   261  		{"/src2*filepath", true},
   262  		{"/search/:query", false},
   263  		{"/search/invalid", true},
   264  		{"/user_:name", false},
   265  		{"/user_x", true},
   266  		{"/user_:name", false},
   267  		{"/id:id", false},
   268  		{"/id/:id", true},
   269  	}
   270  	testRoutes(t, routes)
   271  }
   272  
   273  func TestTreeChildConflict(t *testing.T) {
   274  	routes := []testRoute{
   275  		{"/cmd/vet", false},
   276  		{"/cmd/:tool/:sub", true},
   277  		{"/src/AUTHORS", false},
   278  		{"/src/*filepath", true},
   279  		{"/user_x", false},
   280  		{"/user_:name", true},
   281  		{"/id/:id", false},
   282  		{"/id:id", true},
   283  		{"/:id", true},
   284  		{"/*filepath", true},
   285  	}
   286  	testRoutes(t, routes)
   287  }
   288  
   289  func TestTreeDupliatePath(t *testing.T) {
   290  	tree := &node{}
   291  
   292  	routes := [...]string{
   293  		"/",
   294  		"/doc/",
   295  		"/src/*filepath",
   296  		"/search/:query",
   297  		"/user_:name",
   298  	}
   299  	for _, route := range routes {
   300  		recv := catchPanic(func() {
   301  			tree.addRoute(route, fakeHandler(route), newRouter(), nil)
   302  		})
   303  		if recv != nil {
   304  			t.Fatalf("panic inserting route '%s': %v", route, recv)
   305  		}
   306  
   307  		// Add again
   308  		recv = catchPanic(func() {
   309  			tree.addRoute(route, nil, newRouter(), nil)
   310  		})
   311  		if recv == nil {
   312  			t.Fatalf("no panic while inserting duplicate route '%s", route)
   313  		}
   314  	}
   315  
   316  	//printChildren(tree, "")
   317  
   318  	checkRequests(t, tree, testRequests{
   319  		{"/", false, "/", nil},
   320  		{"/doc/", false, "/doc/", nil},
   321  		{"/src/some/file.png", false, "/src/*filepath", map[string]string{"filepath": "/some/file.png"}},
   322  		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", map[string]string{"query": "someth!ng+in+ünìcodé"}},
   323  		{"/user_gopher", false, "/user_:name", map[string]string{"name": "gopher"}},
   324  	})
   325  }
   326  
   327  func TestEmptyWildcardName(t *testing.T) {
   328  	tree := &node{}
   329  
   330  	routes := [...]string{
   331  		"/user:",
   332  		"/user:/",
   333  		"/cmd/:/",
   334  		"/src/*",
   335  	}
   336  	for _, route := range routes {
   337  		recv := catchPanic(func() {
   338  			tree.addRoute(route, nil, newRouter(), nil)
   339  		})
   340  		if recv == nil {
   341  			t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
   342  		}
   343  	}
   344  }
   345  
   346  func TestTreeCatchAllConflict(t *testing.T) {
   347  	routes := []testRoute{
   348  		{"/src/*filepath/x", true},
   349  		{"/src2/", false},
   350  		{"/src2/*filepath/x", true},
   351  	}
   352  	testRoutes(t, routes)
   353  }
   354  
   355  func TestTreeCatchAllConflictRoot(t *testing.T) {
   356  	routes := []testRoute{
   357  		{"/", false},
   358  		{"/*filepath", true},
   359  	}
   360  	testRoutes(t, routes)
   361  }
   362  
   363  func TestTreeDoubleWildcard(t *testing.T) {
   364  	const panicMsg = "only one wildcard per path segment is allowed"
   365  
   366  	routes := [...]string{
   367  		"/:foo:bar",
   368  		"/:foo:bar/",
   369  		"/:foo*bar",
   370  	}
   371  
   372  	for _, route := range routes {
   373  		tree := &node{}
   374  		recv := catchPanic(func() {
   375  			tree.addRoute(route, nil, newRouter(), nil)
   376  		})
   377  
   378  		if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {
   379  			t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
   380  		}
   381  	}
   382  }
   383  
   384  /*func TestTreeDuplicateWildcard(t *testing.T) {
   385  	tree := &node{}
   386  
   387  	routes := [...]string{
   388  		"/:id/:name/:id",
   389  	}
   390  	for _, route := range routes {
   391  		...
   392  	}
   393  }*/
   394  
   395  func TestTreeTrailingSlashRedirect(t *testing.T) {
   396  	tree := new(node)
   397  	ctx := &Context{
   398  		RequestCtx: new(fasthttp.RequestCtx),
   399  	}
   400  
   401  	routes := [...]string{
   402  		"/hi",
   403  		"/b/",
   404  		"/search/:query",
   405  		"/cmd/:tool/",
   406  		"/src/*filepath",
   407  		"/x",
   408  		"/x/y",
   409  		"/y/",
   410  		"/y/z",
   411  		"/0/:id",
   412  		"/0/:id/1",
   413  		"/1/:id/",
   414  		"/1/:id/2",
   415  		"/aa",
   416  		"/a/",
   417  		"/admin",
   418  		"/admin/:category",
   419  		"/admin/:category/:page",
   420  		"/doc",
   421  		"/doc/go_faq.html",
   422  		"/doc/go1.html",
   423  		"/no/a",
   424  		"/no/b",
   425  		"/api/hello/:name",
   426  	}
   427  	for _, route := range routes {
   428  		recv := catchPanic(func() {
   429  			tree.addRoute(route, fakeHandler(route), newRouter(), nil)
   430  		})
   431  		if recv != nil {
   432  			t.Fatalf("panic inserting route '%s': %v", route, recv)
   433  		}
   434  	}
   435  
   436  	//printChildren(tree, "")
   437  
   438  	tsrRoutes := [...]string{
   439  		"/hi/",
   440  		"/b",
   441  		"/search/gopher/",
   442  		"/cmd/vet",
   443  		"/src",
   444  		"/x/",
   445  		"/y",
   446  		"/0/go/",
   447  		"/1/go",
   448  		"/a",
   449  		"/admin/",
   450  		"/admin/config/",
   451  		"/admin/config/permissions/",
   452  		"/doc/",
   453  	}
   454  	for _, route := range tsrRoutes {
   455  		handler, _, tsr := tree.GetValue(route, ctx, "")
   456  		if handler != nil {
   457  			t.Fatalf("non-nil handler for TSR route '%s", route)
   458  		} else if !tsr {
   459  			t.Errorf("expected TSR recommendation for route '%s'", route)
   460  		}
   461  	}
   462  
   463  	noTsrRoutes := [...]string{
   464  		"/",
   465  		"/no",
   466  		"/no/",
   467  		"/_",
   468  		"/_/",
   469  		"/api/world/abc",
   470  	}
   471  	for _, route := range noTsrRoutes {
   472  		handler, _, tsr := tree.GetValue(route, ctx, "")
   473  		if handler != nil {
   474  			t.Fatalf("non-nil handler for No-TSR route '%s", route)
   475  		} else if tsr {
   476  			t.Errorf("expected no TSR recommendation for route '%s'", route)
   477  		}
   478  	}
   479  }
   480  
   481  func TestTreeRootTrailingSlashRedirect(t *testing.T) {
   482  	tree := &node{}
   483  
   484  	recv := catchPanic(func() {
   485  		tree.addRoute("/:test", fakeHandler("/:test"), newRouter(), nil)
   486  	})
   487  	if recv != nil {
   488  		t.Fatalf("panic inserting test route: %v", recv)
   489  	}
   490  
   491  	handler, _, tsr := tree.GetValue("/", nil, "")
   492  	if handler != nil {
   493  		t.Fatalf("non-nil handler")
   494  	} else if tsr {
   495  		t.Errorf("expected no TSR recommendation")
   496  	}
   497  }
   498  
   499  func TestTreeFindCaseInsensitivePath(t *testing.T) {
   500  	tree := new(node)
   501  
   502  	routes := [...]string{
   503  		"/hi",
   504  		"/b/",
   505  		"/ABC/",
   506  		"/search/:query",
   507  		"/cmd/:tool/",
   508  		"/src/*filepath",
   509  		"/x",
   510  		"/x/y",
   511  		"/y/",
   512  		"/y/z",
   513  		"/0/:id",
   514  		"/0/:id/1",
   515  		"/1/:id/",
   516  		"/1/:id/2",
   517  		"/aa",
   518  		"/a/",
   519  		"/doc",
   520  		"/doc/go_faq.html",
   521  		"/doc/go1.html",
   522  		"/doc/go/away",
   523  		"/no/a",
   524  		"/no/b",
   525  		"/Π",
   526  		"/u/apfêl/",
   527  		"/u/äpfêl/",
   528  		"/u/öpfêl",
   529  		"/v/Äpfêl/",
   530  		"/v/Öpfêl",
   531  		"/w/♬",  // 3 byte
   532  		"/w/♭/", // 3 byte, last byte differs
   533  		"/w/𠜎",  // 4 byte
   534  		"/w/𠜏/", // 4 byte
   535  	}
   536  
   537  	for _, route := range routes {
   538  		recv := catchPanic(func() {
   539  			tree.addRoute(route, fakeHandler(route), newRouter(), nil)
   540  		})
   541  		if recv != nil {
   542  			t.Fatalf("panic inserting route '%s': %v", route, recv)
   543  		}
   544  	}
   545  
   546  	// Check out == in for all registered routes
   547  	// With fixTrailingSlash = true
   548  	for _, route := range routes {
   549  		out, found := tree.FindCaseInsensitivePath(route, true)
   550  		if !found {
   551  			t.Errorf("Route '%s' not found!", route)
   552  		} else if string(out) != route {
   553  			t.Errorf("Wrong result for route '%s': %s", route, string(out))
   554  		}
   555  	}
   556  	// With fixTrailingSlash = false
   557  	for _, route := range routes {
   558  		out, found := tree.FindCaseInsensitivePath(route, false)
   559  		if !found {
   560  			t.Errorf("Route '%s' not found!", route)
   561  		} else if string(out) != route {
   562  			t.Errorf("Wrong result for route '%s': %s", route, string(out))
   563  		}
   564  	}
   565  
   566  	tests := []struct {
   567  		in    string
   568  		out   string
   569  		found bool
   570  		slash bool
   571  	}{
   572  		{"/HI", "/hi", true, false},
   573  		{"/HI/", "/hi", true, true},
   574  		{"/B", "/b/", true, true},
   575  		{"/B/", "/b/", true, false},
   576  		{"/abc", "/ABC/", true, true},
   577  		{"/abc/", "/ABC/", true, false},
   578  		{"/aBc", "/ABC/", true, true},
   579  		{"/aBc/", "/ABC/", true, false},
   580  		{"/abC", "/ABC/", true, true},
   581  		{"/abC/", "/ABC/", true, false},
   582  		{"/SEARCH/QUERY", "/search/QUERY", true, false},
   583  		{"/SEARCH/QUERY/", "/search/QUERY", true, true},
   584  		{"/CMD/TOOL/", "/cmd/TOOL/", true, false},
   585  		{"/CMD/TOOL", "/cmd/TOOL/", true, true},
   586  		{"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
   587  		{"/x/Y", "/x/y", true, false},
   588  		{"/x/Y/", "/x/y", true, true},
   589  		{"/X/y", "/x/y", true, false},
   590  		{"/X/y/", "/x/y", true, true},
   591  		{"/X/Y", "/x/y", true, false},
   592  		{"/X/Y/", "/x/y", true, true},
   593  		{"/Y/", "/y/", true, false},
   594  		{"/Y", "/y/", true, true},
   595  		{"/Y/z", "/y/z", true, false},
   596  		{"/Y/z/", "/y/z", true, true},
   597  		{"/Y/Z", "/y/z", true, false},
   598  		{"/Y/Z/", "/y/z", true, true},
   599  		{"/y/Z", "/y/z", true, false},
   600  		{"/y/Z/", "/y/z", true, true},
   601  		{"/Aa", "/aa", true, false},
   602  		{"/Aa/", "/aa", true, true},
   603  		{"/AA", "/aa", true, false},
   604  		{"/AA/", "/aa", true, true},
   605  		{"/aA", "/aa", true, false},
   606  		{"/aA/", "/aa", true, true},
   607  		{"/A/", "/a/", true, false},
   608  		{"/A", "/a/", true, true},
   609  		{"/DOC", "/doc", true, false},
   610  		{"/DOC/", "/doc", true, true},
   611  		{"/NO", "", false, true},
   612  		{"/DOC/GO", "", false, true},
   613  		{"/π", "/Π", true, false},
   614  		{"/π/", "/Π", true, true},
   615  		{"/u/ÄPFÊL/", "/u/äpfêl/", true, false},
   616  		{"/u/ÄPFÊL", "/u/äpfêl/", true, true},
   617  		{"/u/ÖPFÊL/", "/u/öpfêl", true, true},
   618  		{"/u/ÖPFÊL", "/u/öpfêl", true, false},
   619  		{"/v/äpfêL/", "/v/Äpfêl/", true, false},
   620  		{"/v/äpfêL", "/v/Äpfêl/", true, true},
   621  		{"/v/öpfêL/", "/v/Öpfêl", true, true},
   622  		{"/v/öpfêL", "/v/Öpfêl", true, false},
   623  		{"/w/♬/", "/w/♬", true, true},
   624  		{"/w/♭", "/w/♭/", true, true},
   625  		{"/w/𠜎/", "/w/𠜎", true, true},
   626  		{"/w/𠜏", "/w/𠜏/", true, true},
   627  	}
   628  	// With fixTrailingSlash = true
   629  	for _, test := range tests {
   630  		out, found := tree.FindCaseInsensitivePath(test.in, true)
   631  		if found != test.found || (found && (string(out) != test.out)) {
   632  			t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
   633  				test.in, string(out), found, test.out, test.found)
   634  			return
   635  		}
   636  	}
   637  	// With fixTrailingSlash = false
   638  	for _, test := range tests {
   639  		out, found := tree.FindCaseInsensitivePath(test.in, false)
   640  		if test.slash {
   641  			if found { // test needs a trailingSlash fix. It must not be found!
   642  				t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
   643  			}
   644  		} else {
   645  			if found != test.found || (found && (string(out) != test.out)) {
   646  				t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
   647  					test.in, string(out), found, test.out, test.found)
   648  				return
   649  			}
   650  		}
   651  	}
   652  }
   653  
   654  func TestTreeInvalidNodeType(t *testing.T) {
   655  	const panicMsg = "invalid node type"
   656  
   657  	tree := new(node)
   658  	tree.addRoute("/", fakeHandler("/"), newRouter(), nil)
   659  	tree.addRoute("/:page", fakeHandler("/:page"), newRouter(), nil)
   660  
   661  	// set invalid node type
   662  	tree.children[0].nType = 42
   663  
   664  	// normal lookup
   665  	recv := catchPanic(func() {
   666  		tree.GetValue("/test", nil, "")
   667  	})
   668  	if rs, ok := recv.(string); !ok || rs != panicMsg {
   669  		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
   670  	}
   671  
   672  	// case-insensitive lookup
   673  	recv = catchPanic(func() {
   674  		tree.FindCaseInsensitivePath("/test", true)
   675  	})
   676  	if rs, ok := recv.(string); !ok || rs != panicMsg {
   677  		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
   678  	}
   679  }