...

Source file src/github.com/gin-gonic/gin/tree_test.go

Documentation: github.com/gin-gonic/gin

     1  // Copyright 2013 Julien Schmidt. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be found
     3  // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE
     4  
     5  package gin
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  // Used as a workaround since we can't compare functions or their addresses
    16  var fakeHandlerValue string
    17  
    18  func fakeHandler(val string) HandlersChain {
    19  	return HandlersChain{func(c *Context) {
    20  		fakeHandlerValue = val
    21  	}}
    22  }
    23  
    24  type testRequests []struct {
    25  	path       string
    26  	nilHandler bool
    27  	route      string
    28  	ps         Params
    29  }
    30  
    31  func getParams() *Params {
    32  	ps := make(Params, 0, 20)
    33  	return &ps
    34  }
    35  
    36  func getSkippedNodes() *[]skippedNode {
    37  	ps := make([]skippedNode, 0, 20)
    38  	return &ps
    39  }
    40  
    41  func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
    42  	unescape := false
    43  	if len(unescapes) >= 1 {
    44  		unescape = unescapes[0]
    45  	}
    46  
    47  	for _, request := range requests {
    48  		value := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape)
    49  
    50  		if value.handlers == nil {
    51  			if !request.nilHandler {
    52  				t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
    53  			}
    54  		} else if request.nilHandler {
    55  			t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
    56  		} else {
    57  			value.handlers[0](nil)
    58  			if fakeHandlerValue != request.route {
    59  				t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
    60  			}
    61  		}
    62  
    63  		if value.params != nil {
    64  			if !reflect.DeepEqual(*value.params, request.ps) {
    65  				t.Errorf("Params mismatch for route '%s'", request.path)
    66  			}
    67  		}
    68  
    69  	}
    70  }
    71  
    72  func checkPriorities(t *testing.T, n *node) uint32 {
    73  	var prio uint32
    74  	for i := range n.children {
    75  		prio += checkPriorities(t, n.children[i])
    76  	}
    77  
    78  	if n.handlers != nil {
    79  		prio++
    80  	}
    81  
    82  	if n.priority != prio {
    83  		t.Errorf(
    84  			"priority mismatch for node '%s': is %d, should be %d",
    85  			n.path, n.priority, prio,
    86  		)
    87  	}
    88  
    89  	return prio
    90  }
    91  
    92  func TestCountParams(t *testing.T) {
    93  	if countParams("/path/:param1/static/*catch-all") != 2 {
    94  		t.Fail()
    95  	}
    96  	if countParams(strings.Repeat("/:param", 256)) != 256 {
    97  		t.Fail()
    98  	}
    99  }
   100  
   101  func TestTreeAddAndGet(t *testing.T) {
   102  	tree := &node{}
   103  
   104  	routes := [...]string{
   105  		"/hi",
   106  		"/contact",
   107  		"/co",
   108  		"/c",
   109  		"/a",
   110  		"/ab",
   111  		"/doc/",
   112  		"/doc/go_faq.html",
   113  		"/doc/go1.html",
   114  		"/α",
   115  		"/β",
   116  	}
   117  	for _, route := range routes {
   118  		tree.addRoute(route, fakeHandler(route))
   119  	}
   120  
   121  	checkRequests(t, tree, testRequests{
   122  		{"/a", false, "/a", nil},
   123  		{"/", true, "", nil},
   124  		{"/hi", false, "/hi", nil},
   125  		{"/contact", false, "/contact", nil},
   126  		{"/co", false, "/co", nil},
   127  		{"/con", true, "", nil},  // key mismatch
   128  		{"/cona", true, "", nil}, // key mismatch
   129  		{"/no", true, "", nil},   // no matching child
   130  		{"/ab", false, "/ab", nil},
   131  		{"/α", false, "/α", nil},
   132  		{"/β", false, "/β", nil},
   133  	})
   134  
   135  	checkPriorities(t, tree)
   136  }
   137  
   138  func TestTreeWildcard(t *testing.T) {
   139  	tree := &node{}
   140  
   141  	routes := [...]string{
   142  		"/",
   143  		"/cmd/:tool/",
   144  		"/cmd/:tool/:sub",
   145  		"/cmd/whoami",
   146  		"/cmd/whoami/root",
   147  		"/cmd/whoami/root/",
   148  		"/src/*filepath",
   149  		"/search/",
   150  		"/search/:query",
   151  		"/search/gin-gonic",
   152  		"/search/google",
   153  		"/user_:name",
   154  		"/user_:name/about",
   155  		"/files/:dir/*filepath",
   156  		"/doc/",
   157  		"/doc/go_faq.html",
   158  		"/doc/go1.html",
   159  		"/info/:user/public",
   160  		"/info/:user/project/:project",
   161  		"/info/:user/project/golang",
   162  		"/aa/*xx",
   163  		"/ab/*xx",
   164  		"/:cc",
   165  		"/c1/:dd/e",
   166  		"/c1/:dd/e1",
   167  		"/:cc/cc",
   168  		"/:cc/:dd/ee",
   169  		"/:cc/:dd/:ee/ff",
   170  		"/:cc/:dd/:ee/:ff/gg",
   171  		"/:cc/:dd/:ee/:ff/:gg/hh",
   172  		"/get/test/abc/",
   173  		"/get/:param/abc/",
   174  		"/something/:paramname/thirdthing",
   175  		"/something/secondthing/test",
   176  		"/get/abc",
   177  		"/get/:param",
   178  		"/get/abc/123abc",
   179  		"/get/abc/:param",
   180  		"/get/abc/123abc/xxx8",
   181  		"/get/abc/123abc/:param",
   182  		"/get/abc/123abc/xxx8/1234",
   183  		"/get/abc/123abc/xxx8/:param",
   184  		"/get/abc/123abc/xxx8/1234/ffas",
   185  		"/get/abc/123abc/xxx8/1234/:param",
   186  		"/get/abc/123abc/xxx8/1234/kkdd/12c",
   187  		"/get/abc/123abc/xxx8/1234/kkdd/:param",
   188  		"/get/abc/:param/test",
   189  		"/get/abc/123abd/:param",
   190  		"/get/abc/123abddd/:param",
   191  		"/get/abc/123/:param",
   192  		"/get/abc/123abg/:param",
   193  		"/get/abc/123abf/:param",
   194  		"/get/abc/123abfff/:param",
   195  	}
   196  	for _, route := range routes {
   197  		tree.addRoute(route, fakeHandler(route))
   198  	}
   199  
   200  	checkRequests(t, tree, testRequests{
   201  		{"/", false, "/", nil},
   202  		{"/cmd/test", true, "/cmd/:tool/", Params{Param{"tool", "test"}}},
   203  		{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
   204  		{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}},
   205  		{"/cmd/who", true, "/cmd/:tool/", Params{Param{"tool", "who"}}},
   206  		{"/cmd/who/", false, "/cmd/:tool/", Params{Param{"tool", "who"}}},
   207  		{"/cmd/whoami", false, "/cmd/whoami", nil},
   208  		{"/cmd/whoami/", true, "/cmd/whoami", nil},
   209  		{"/cmd/whoami/r", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
   210  		{"/cmd/whoami/r/", true, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
   211  		{"/cmd/whoami/root", false, "/cmd/whoami/root", nil},
   212  		{"/cmd/whoami/root/", false, "/cmd/whoami/root/", nil},
   213  		{"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}},
   214  		{"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
   215  		{"/search/", false, "/search/", nil},
   216  		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
   217  		{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
   218  		{"/search/gin", false, "/search/:query", Params{Param{"query", "gin"}}},
   219  		{"/search/gin-gonic", false, "/search/gin-gonic", nil},
   220  		{"/search/google", false, "/search/google", nil},
   221  		{"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}},
   222  		{"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}},
   223  		{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}},
   224  		{"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}},
   225  		{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
   226  		{"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}},
   227  		{"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}},
   228  		{"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}},
   229  		{"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}},
   230  		// * Error with argument being intercepted
   231  		// new PR handle (/all /all/cc /a/cc)
   232  		// fix PR: https://github.com/gin-gonic/gin/pull/2796
   233  		{"/all", false, "/:cc", Params{Param{Key: "cc", Value: "all"}}},
   234  		{"/d", false, "/:cc", Params{Param{Key: "cc", Value: "d"}}},
   235  		{"/ad", false, "/:cc", Params{Param{Key: "cc", Value: "ad"}}},
   236  		{"/dd", false, "/:cc", Params{Param{Key: "cc", Value: "dd"}}},
   237  		{"/dddaa", false, "/:cc", Params{Param{Key: "cc", Value: "dddaa"}}},
   238  		{"/aa", false, "/:cc", Params{Param{Key: "cc", Value: "aa"}}},
   239  		{"/aaa", false, "/:cc", Params{Param{Key: "cc", Value: "aaa"}}},
   240  		{"/aaa/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "aaa"}}},
   241  		{"/ab", false, "/:cc", Params{Param{Key: "cc", Value: "ab"}}},
   242  		{"/abb", false, "/:cc", Params{Param{Key: "cc", Value: "abb"}}},
   243  		{"/abb/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "abb"}}},
   244  		{"/allxxxx", false, "/:cc", Params{Param{Key: "cc", Value: "allxxxx"}}},
   245  		{"/alldd", false, "/:cc", Params{Param{Key: "cc", Value: "alldd"}}},
   246  		{"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "all"}}},
   247  		{"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "a"}}},
   248  		{"/c1/d/e", false, "/c1/:dd/e", Params{Param{Key: "dd", Value: "d"}}},
   249  		{"/c1/d/e1", false, "/c1/:dd/e1", Params{Param{Key: "dd", Value: "d"}}},
   250  		{"/c1/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c1"}, Param{Key: "dd", Value: "d"}}},
   251  		{"/cc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "cc"}}},
   252  		{"/ccc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ccc"}}},
   253  		{"/deedwjfs/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "deedwjfs"}}},
   254  		{"/acllcc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "acllcc"}}},
   255  		{"/get/test/abc/", false, "/get/test/abc/", nil},
   256  		{"/get/te/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "te"}}},
   257  		{"/get/testaa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "testaa"}}},
   258  		{"/get/xx/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "xx"}}},
   259  		{"/get/tt/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "tt"}}},
   260  		{"/get/a/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "a"}}},
   261  		{"/get/t/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "t"}}},
   262  		{"/get/aa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "aa"}}},
   263  		{"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}},
   264  		{"/something/secondthing/test", false, "/something/secondthing/test", nil},
   265  		{"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}},
   266  		{"/something/secondthingaaaa/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "secondthingaaaa"}}},
   267  		{"/something/se/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "se"}}},
   268  		{"/something/s/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "s"}}},
   269  		{"/c/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}}},
   270  		{"/c/d/e/ff", false, "/:cc/:dd/:ee/ff", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}}},
   271  		{"/c/d/e/f/gg", false, "/:cc/:dd/:ee/:ff/gg", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}}},
   272  		{"/c/d/e/f/g/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}, Param{Key: "gg", Value: "g"}}},
   273  		{"/cc/dd/ee/ff/gg/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "cc"}, Param{Key: "dd", Value: "dd"}, Param{Key: "ee", Value: "ee"}, Param{Key: "ff", Value: "ff"}, Param{Key: "gg", Value: "gg"}}},
   274  		{"/get/abc", false, "/get/abc", nil},
   275  		{"/get/a", false, "/get/:param", Params{Param{Key: "param", Value: "a"}}},
   276  		{"/get/abz", false, "/get/:param", Params{Param{Key: "param", Value: "abz"}}},
   277  		{"/get/12a", false, "/get/:param", Params{Param{Key: "param", Value: "12a"}}},
   278  		{"/get/abcd", false, "/get/:param", Params{Param{Key: "param", Value: "abcd"}}},
   279  		{"/get/abc/123abc", false, "/get/abc/123abc", nil},
   280  		{"/get/abc/12", false, "/get/abc/:param", Params{Param{Key: "param", Value: "12"}}},
   281  		{"/get/abc/123ab", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123ab"}}},
   282  		{"/get/abc/xyz", false, "/get/abc/:param", Params{Param{Key: "param", Value: "xyz"}}},
   283  		{"/get/abc/123abcddxx", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123abcddxx"}}},
   284  		{"/get/abc/123abc/xxx8", false, "/get/abc/123abc/xxx8", nil},
   285  		{"/get/abc/123abc/x", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "x"}}},
   286  		{"/get/abc/123abc/xxx", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx"}}},
   287  		{"/get/abc/123abc/abc", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "abc"}}},
   288  		{"/get/abc/123abc/xxx8xxas", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx8xxas"}}},
   289  		{"/get/abc/123abc/xxx8/1234", false, "/get/abc/123abc/xxx8/1234", nil},
   290  		{"/get/abc/123abc/xxx8/1", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1"}}},
   291  		{"/get/abc/123abc/xxx8/123", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "123"}}},
   292  		{"/get/abc/123abc/xxx8/78k", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "78k"}}},
   293  		{"/get/abc/123abc/xxx8/1234xxxd", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1234xxxd"}}},
   294  		{"/get/abc/123abc/xxx8/1234/ffas", false, "/get/abc/123abc/xxx8/1234/ffas", nil},
   295  		{"/get/abc/123abc/xxx8/1234/f", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "f"}}},
   296  		{"/get/abc/123abc/xxx8/1234/ffa", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffa"}}},
   297  		{"/get/abc/123abc/xxx8/1234/kka", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "kka"}}},
   298  		{"/get/abc/123abc/xxx8/1234/ffas321", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffas321"}}},
   299  		{"/get/abc/123abc/xxx8/1234/kkdd/12c", false, "/get/abc/123abc/xxx8/1234/kkdd/12c", nil},
   300  		{"/get/abc/123abc/xxx8/1234/kkdd/1", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "1"}}},
   301  		{"/get/abc/123abc/xxx8/1234/kkdd/12", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12"}}},
   302  		{"/get/abc/123abc/xxx8/1234/kkdd/12b", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12b"}}},
   303  		{"/get/abc/123abc/xxx8/1234/kkdd/34", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "34"}}},
   304  		{"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12c2e3"}}},
   305  		{"/get/abc/12/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "12"}}},
   306  		{"/get/abc/123abdd/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdd"}}},
   307  		{"/get/abc/123abdddf/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdddf"}}},
   308  		{"/get/abc/123ab/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123ab"}}},
   309  		{"/get/abc/123abgg/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abgg"}}},
   310  		{"/get/abc/123abff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abff"}}},
   311  		{"/get/abc/123abffff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abffff"}}},
   312  		{"/get/abc/123abd/test", false, "/get/abc/123abd/:param", Params{Param{Key: "param", Value: "test"}}},
   313  		{"/get/abc/123abddd/test", false, "/get/abc/123abddd/:param", Params{Param{Key: "param", Value: "test"}}},
   314  		{"/get/abc/123/test22", false, "/get/abc/123/:param", Params{Param{Key: "param", Value: "test22"}}},
   315  		{"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}},
   316  		{"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}},
   317  		{"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}},
   318  	})
   319  
   320  	checkPriorities(t, tree)
   321  }
   322  
   323  func TestUnescapeParameters(t *testing.T) {
   324  	tree := &node{}
   325  
   326  	routes := [...]string{
   327  		"/",
   328  		"/cmd/:tool/:sub",
   329  		"/cmd/:tool/",
   330  		"/src/*filepath",
   331  		"/search/:query",
   332  		"/files/:dir/*filepath",
   333  		"/info/:user/project/:project",
   334  		"/info/:user",
   335  	}
   336  	for _, route := range routes {
   337  		tree.addRoute(route, fakeHandler(route))
   338  	}
   339  
   340  	unescape := true
   341  	checkRequests(t, tree, testRequests{
   342  		{"/", false, "/", nil},
   343  		{"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}},
   344  		{"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}},
   345  		{"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
   346  		{"/src/some/file+test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file test.png"}}},
   347  		{"/src/some/file++++%%%%test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file++++%%%%test.png"}}},
   348  		{"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file/test.png"}}},
   349  		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng in ünìcodé"}}},
   350  		{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
   351  		{"/info/slash%2Fgordon", false, "/info/:user", Params{Param{Key: "user", Value: "slash/gordon"}}},
   352  		{"/info/slash%2Fgordon/project/Project%20%231", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "slash/gordon"}, Param{Key: "project", Value: "Project #1"}}},
   353  		{"/info/slash%%%%", false, "/info/:user", Params{Param{Key: "user", Value: "slash%%%%"}}},
   354  		{"/info/slash%%%%2Fgordon/project/Project%%%%20%231", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "slash%%%%2Fgordon"}, Param{Key: "project", Value: "Project%%%%20%231"}}},
   355  	}, unescape)
   356  
   357  	checkPriorities(t, tree)
   358  }
   359  
   360  func catchPanic(testFunc func()) (recv any) {
   361  	defer func() {
   362  		recv = recover()
   363  	}()
   364  
   365  	testFunc()
   366  	return
   367  }
   368  
   369  type testRoute struct {
   370  	path     string
   371  	conflict bool
   372  }
   373  
   374  func testRoutes(t *testing.T, routes []testRoute) {
   375  	tree := &node{}
   376  
   377  	for _, route := range routes {
   378  		recv := catchPanic(func() {
   379  			tree.addRoute(route.path, nil)
   380  		})
   381  
   382  		if route.conflict {
   383  			if recv == nil {
   384  				t.Errorf("no panic for conflicting route '%s'", route.path)
   385  			}
   386  		} else if recv != nil {
   387  			t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
   388  		}
   389  	}
   390  }
   391  
   392  func TestTreeWildcardConflict(t *testing.T) {
   393  	routes := []testRoute{
   394  		{"/cmd/:tool/:sub", false},
   395  		{"/cmd/vet", false},
   396  		{"/foo/bar", false},
   397  		{"/foo/:name", false},
   398  		{"/foo/:names", true},
   399  		{"/cmd/*path", true},
   400  		{"/cmd/:badvar", true},
   401  		{"/cmd/:tool/names", false},
   402  		{"/cmd/:tool/:badsub/details", true},
   403  		{"/src/*filepath", false},
   404  		{"/src/:file", true},
   405  		{"/src/static.json", true},
   406  		{"/src/*filepathx", true},
   407  		{"/src/", true},
   408  		{"/src/foo/bar", true},
   409  		{"/src1/", false},
   410  		{"/src1/*filepath", true},
   411  		{"/src2*filepath", true},
   412  		{"/src2/*filepath", false},
   413  		{"/search/:query", false},
   414  		{"/search/valid", false},
   415  		{"/user_:name", false},
   416  		{"/user_x", false},
   417  		{"/user_:name", false},
   418  		{"/id:id", false},
   419  		{"/id/:id", false},
   420  	}
   421  	testRoutes(t, routes)
   422  }
   423  
   424  func TestCatchAllAfterSlash(t *testing.T) {
   425  	routes := []testRoute{
   426  		{"/non-leading-*catchall", true},
   427  	}
   428  	testRoutes(t, routes)
   429  }
   430  
   431  func TestTreeChildConflict(t *testing.T) {
   432  	routes := []testRoute{
   433  		{"/cmd/vet", false},
   434  		{"/cmd/:tool", false},
   435  		{"/cmd/:tool/:sub", false},
   436  		{"/cmd/:tool/misc", false},
   437  		{"/cmd/:tool/:othersub", true},
   438  		{"/src/AUTHORS", false},
   439  		{"/src/*filepath", true},
   440  		{"/user_x", false},
   441  		{"/user_:name", false},
   442  		{"/id/:id", false},
   443  		{"/id:id", false},
   444  		{"/:id", false},
   445  		{"/*filepath", true},
   446  	}
   447  	testRoutes(t, routes)
   448  }
   449  
   450  func TestTreeDuplicatePath(t *testing.T) {
   451  	tree := &node{}
   452  
   453  	routes := [...]string{
   454  		"/",
   455  		"/doc/",
   456  		"/src/*filepath",
   457  		"/search/:query",
   458  		"/user_:name",
   459  	}
   460  	for _, route := range routes {
   461  		recv := catchPanic(func() {
   462  			tree.addRoute(route, fakeHandler(route))
   463  		})
   464  		if recv != nil {
   465  			t.Fatalf("panic inserting route '%s': %v", route, recv)
   466  		}
   467  
   468  		// Add again
   469  		recv = catchPanic(func() {
   470  			tree.addRoute(route, nil)
   471  		})
   472  		if recv == nil {
   473  			t.Fatalf("no panic while inserting duplicate route '%s", route)
   474  		}
   475  	}
   476  
   477  	//printChildren(tree, "")
   478  
   479  	checkRequests(t, tree, testRequests{
   480  		{"/", false, "/", nil},
   481  		{"/doc/", false, "/doc/", nil},
   482  		{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
   483  		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
   484  		{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
   485  	})
   486  }
   487  
   488  func TestEmptyWildcardName(t *testing.T) {
   489  	tree := &node{}
   490  
   491  	routes := [...]string{
   492  		"/user:",
   493  		"/user:/",
   494  		"/cmd/:/",
   495  		"/src/*",
   496  	}
   497  	for _, route := range routes {
   498  		recv := catchPanic(func() {
   499  			tree.addRoute(route, nil)
   500  		})
   501  		if recv == nil {
   502  			t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
   503  		}
   504  	}
   505  }
   506  
   507  func TestTreeCatchAllConflict(t *testing.T) {
   508  	routes := []testRoute{
   509  		{"/src/*filepath/x", true},
   510  		{"/src2/", false},
   511  		{"/src2/*filepath/x", true},
   512  		{"/src3/*filepath", false},
   513  		{"/src3/*filepath/x", true},
   514  	}
   515  	testRoutes(t, routes)
   516  }
   517  
   518  func TestTreeCatchAllConflictRoot(t *testing.T) {
   519  	routes := []testRoute{
   520  		{"/", false},
   521  		{"/*filepath", true},
   522  	}
   523  	testRoutes(t, routes)
   524  }
   525  
   526  func TestTreeCatchMaxParams(t *testing.T) {
   527  	tree := &node{}
   528  	var route = "/cmd/*filepath"
   529  	tree.addRoute(route, fakeHandler(route))
   530  }
   531  
   532  func TestTreeDoubleWildcard(t *testing.T) {
   533  	const panicMsg = "only one wildcard per path segment is allowed"
   534  
   535  	routes := [...]string{
   536  		"/:foo:bar",
   537  		"/:foo:bar/",
   538  		"/:foo*bar",
   539  	}
   540  
   541  	for _, route := range routes {
   542  		tree := &node{}
   543  		recv := catchPanic(func() {
   544  			tree.addRoute(route, nil)
   545  		})
   546  
   547  		if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {
   548  			t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
   549  		}
   550  	}
   551  }
   552  
   553  /*func TestTreeDuplicateWildcard(t *testing.T) {
   554  	tree := &node{}
   555  	routes := [...]string{
   556  		"/:id/:name/:id",
   557  	}
   558  	for _, route := range routes {
   559  		...
   560  	}
   561  }*/
   562  
   563  func TestTreeTrailingSlashRedirect(t *testing.T) {
   564  	tree := &node{}
   565  
   566  	routes := [...]string{
   567  		"/hi",
   568  		"/b/",
   569  		"/search/:query",
   570  		"/cmd/:tool/",
   571  		"/src/*filepath",
   572  		"/x",
   573  		"/x/y",
   574  		"/y/",
   575  		"/y/z",
   576  		"/0/:id",
   577  		"/0/:id/1",
   578  		"/1/:id/",
   579  		"/1/:id/2",
   580  		"/aa",
   581  		"/a/",
   582  		"/admin",
   583  		"/admin/:category",
   584  		"/admin/:category/:page",
   585  		"/doc",
   586  		"/doc/go_faq.html",
   587  		"/doc/go1.html",
   588  		"/no/a",
   589  		"/no/b",
   590  		"/api/:page/:name",
   591  		"/api/hello/:name/bar/",
   592  		"/api/bar/:name",
   593  		"/api/baz/foo",
   594  		"/api/baz/foo/bar",
   595  		"/blog/:p",
   596  		"/posts/:b/:c",
   597  		"/posts/b/:c/d/",
   598  		"/vendor/:x/*y",
   599  	}
   600  	for _, route := range routes {
   601  		recv := catchPanic(func() {
   602  			tree.addRoute(route, fakeHandler(route))
   603  		})
   604  		if recv != nil {
   605  			t.Fatalf("panic inserting route '%s': %v", route, recv)
   606  		}
   607  	}
   608  
   609  	tsrRoutes := [...]string{
   610  		"/hi/",
   611  		"/b",
   612  		"/search/gopher/",
   613  		"/cmd/vet",
   614  		"/src",
   615  		"/x/",
   616  		"/y",
   617  		"/0/go/",
   618  		"/1/go",
   619  		"/a",
   620  		"/admin/",
   621  		"/admin/config/",
   622  		"/admin/config/permissions/",
   623  		"/doc/",
   624  		"/admin/static/",
   625  		"/admin/cfg/",
   626  		"/admin/cfg/users/",
   627  		"/api/hello/x/bar",
   628  		"/api/baz/foo/",
   629  		"/api/baz/bax/",
   630  		"/api/bar/huh/",
   631  		"/api/baz/foo/bar/",
   632  		"/api/world/abc/",
   633  		"/blog/pp/",
   634  		"/posts/b/c/d",
   635  		"/vendor/x",
   636  	}
   637  
   638  	for _, route := range tsrRoutes {
   639  		value := tree.getValue(route, nil, getSkippedNodes(), false)
   640  		if value.handlers != nil {
   641  			t.Fatalf("non-nil handler for TSR route '%s", route)
   642  		} else if !value.tsr {
   643  			t.Errorf("expected TSR recommendation for route '%s'", route)
   644  		}
   645  	}
   646  
   647  	noTsrRoutes := [...]string{
   648  		"/",
   649  		"/no",
   650  		"/no/",
   651  		"/_",
   652  		"/_/",
   653  		"/api",
   654  		"/api/",
   655  		"/api/hello/x/foo",
   656  		"/api/baz/foo/bad",
   657  		"/foo/p/p",
   658  	}
   659  	for _, route := range noTsrRoutes {
   660  		value := tree.getValue(route, nil, getSkippedNodes(), false)
   661  		if value.handlers != nil {
   662  			t.Fatalf("non-nil handler for No-TSR route '%s", route)
   663  		} else if value.tsr {
   664  			t.Errorf("expected no TSR recommendation for route '%s'", route)
   665  		}
   666  	}
   667  }
   668  
   669  func TestTreeRootTrailingSlashRedirect(t *testing.T) {
   670  	tree := &node{}
   671  
   672  	recv := catchPanic(func() {
   673  		tree.addRoute("/:test", fakeHandler("/:test"))
   674  	})
   675  	if recv != nil {
   676  		t.Fatalf("panic inserting test route: %v", recv)
   677  	}
   678  
   679  	value := tree.getValue("/", nil, getSkippedNodes(), false)
   680  	if value.handlers != nil {
   681  		t.Fatalf("non-nil handler")
   682  	} else if value.tsr {
   683  		t.Errorf("expected no TSR recommendation")
   684  	}
   685  }
   686  
   687  func TestRedirectTrailingSlash(t *testing.T) {
   688  	var data = []struct {
   689  		path string
   690  	}{
   691  		{"/hello/:name"},
   692  		{"/hello/:name/123"},
   693  		{"/hello/:name/234"},
   694  	}
   695  
   696  	node := &node{}
   697  	for _, item := range data {
   698  		node.addRoute(item.path, fakeHandler("test"))
   699  	}
   700  
   701  	value := node.getValue("/hello/abx/", nil, getSkippedNodes(), false)
   702  	if value.tsr != true {
   703  		t.Fatalf("want true, is false")
   704  	}
   705  }
   706  
   707  func TestTreeFindCaseInsensitivePath(t *testing.T) {
   708  	tree := &node{}
   709  
   710  	longPath := "/l" + strings.Repeat("o", 128) + "ng"
   711  	lOngPath := "/l" + strings.Repeat("O", 128) + "ng/"
   712  
   713  	routes := [...]string{
   714  		"/hi",
   715  		"/b/",
   716  		"/ABC/",
   717  		"/search/:query",
   718  		"/cmd/:tool/",
   719  		"/src/*filepath",
   720  		"/x",
   721  		"/x/y",
   722  		"/y/",
   723  		"/y/z",
   724  		"/0/:id",
   725  		"/0/:id/1",
   726  		"/1/:id/",
   727  		"/1/:id/2",
   728  		"/aa",
   729  		"/a/",
   730  		"/doc",
   731  		"/doc/go_faq.html",
   732  		"/doc/go1.html",
   733  		"/doc/go/away",
   734  		"/no/a",
   735  		"/no/b",
   736  		"/Π",
   737  		"/u/apfêl/",
   738  		"/u/äpfêl/",
   739  		"/u/öpfêl",
   740  		"/v/Äpfêl/",
   741  		"/v/Öpfêl",
   742  		"/w/♬",  // 3 byte
   743  		"/w/♭/", // 3 byte, last byte differs
   744  		"/w/𠜎",  // 4 byte
   745  		"/w/𠜏/", // 4 byte
   746  		longPath,
   747  	}
   748  
   749  	for _, route := range routes {
   750  		recv := catchPanic(func() {
   751  			tree.addRoute(route, fakeHandler(route))
   752  		})
   753  		if recv != nil {
   754  			t.Fatalf("panic inserting route '%s': %v", route, recv)
   755  		}
   756  	}
   757  
   758  	// Check out == in for all registered routes
   759  	// With fixTrailingSlash = true
   760  	for _, route := range routes {
   761  		out, found := tree.findCaseInsensitivePath(route, true)
   762  		if !found {
   763  			t.Errorf("Route '%s' not found!", route)
   764  		} else if string(out) != route {
   765  			t.Errorf("Wrong result for route '%s': %s", route, string(out))
   766  		}
   767  	}
   768  	// With fixTrailingSlash = false
   769  	for _, route := range routes {
   770  		out, found := tree.findCaseInsensitivePath(route, false)
   771  		if !found {
   772  			t.Errorf("Route '%s' not found!", route)
   773  		} else if string(out) != route {
   774  			t.Errorf("Wrong result for route '%s': %s", route, string(out))
   775  		}
   776  	}
   777  
   778  	tests := []struct {
   779  		in    string
   780  		out   string
   781  		found bool
   782  		slash bool
   783  	}{
   784  		{"/HI", "/hi", true, false},
   785  		{"/HI/", "/hi", true, true},
   786  		{"/B", "/b/", true, true},
   787  		{"/B/", "/b/", true, false},
   788  		{"/abc", "/ABC/", true, true},
   789  		{"/abc/", "/ABC/", true, false},
   790  		{"/aBc", "/ABC/", true, true},
   791  		{"/aBc/", "/ABC/", true, false},
   792  		{"/abC", "/ABC/", true, true},
   793  		{"/abC/", "/ABC/", true, false},
   794  		{"/SEARCH/QUERY", "/search/QUERY", true, false},
   795  		{"/SEARCH/QUERY/", "/search/QUERY", true, true},
   796  		{"/CMD/TOOL/", "/cmd/TOOL/", true, false},
   797  		{"/CMD/TOOL", "/cmd/TOOL/", true, true},
   798  		{"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
   799  		{"/x/Y", "/x/y", true, false},
   800  		{"/x/Y/", "/x/y", true, true},
   801  		{"/X/y", "/x/y", true, false},
   802  		{"/X/y/", "/x/y", true, true},
   803  		{"/X/Y", "/x/y", true, false},
   804  		{"/X/Y/", "/x/y", true, true},
   805  		{"/Y/", "/y/", true, false},
   806  		{"/Y", "/y/", true, true},
   807  		{"/Y/z", "/y/z", true, false},
   808  		{"/Y/z/", "/y/z", true, true},
   809  		{"/Y/Z", "/y/z", true, false},
   810  		{"/Y/Z/", "/y/z", true, true},
   811  		{"/y/Z", "/y/z", true, false},
   812  		{"/y/Z/", "/y/z", true, true},
   813  		{"/Aa", "/aa", true, false},
   814  		{"/Aa/", "/aa", true, true},
   815  		{"/AA", "/aa", true, false},
   816  		{"/AA/", "/aa", true, true},
   817  		{"/aA", "/aa", true, false},
   818  		{"/aA/", "/aa", true, true},
   819  		{"/A/", "/a/", true, false},
   820  		{"/A", "/a/", true, true},
   821  		{"/DOC", "/doc", true, false},
   822  		{"/DOC/", "/doc", true, true},
   823  		{"/NO", "", false, true},
   824  		{"/DOC/GO", "", false, true},
   825  		{"/π", "/Π", true, false},
   826  		{"/π/", "/Π", true, true},
   827  		{"/u/ÄPFÊL/", "/u/äpfêl/", true, false},
   828  		{"/u/ÄPFÊL", "/u/äpfêl/", true, true},
   829  		{"/u/ÖPFÊL/", "/u/öpfêl", true, true},
   830  		{"/u/ÖPFÊL", "/u/öpfêl", true, false},
   831  		{"/v/äpfêL/", "/v/Äpfêl/", true, false},
   832  		{"/v/äpfêL", "/v/Äpfêl/", true, true},
   833  		{"/v/öpfêL/", "/v/Öpfêl", true, true},
   834  		{"/v/öpfêL", "/v/Öpfêl", true, false},
   835  		{"/w/♬/", "/w/♬", true, true},
   836  		{"/w/♭", "/w/♭/", true, true},
   837  		{"/w/𠜎/", "/w/𠜎", true, true},
   838  		{"/w/𠜏", "/w/𠜏/", true, true},
   839  		{lOngPath, longPath, true, true},
   840  	}
   841  	// With fixTrailingSlash = true
   842  	for _, test := range tests {
   843  		out, found := tree.findCaseInsensitivePath(test.in, true)
   844  		if found != test.found || (found && (string(out) != test.out)) {
   845  			t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
   846  				test.in, string(out), found, test.out, test.found)
   847  			return
   848  		}
   849  	}
   850  	// With fixTrailingSlash = false
   851  	for _, test := range tests {
   852  		out, found := tree.findCaseInsensitivePath(test.in, false)
   853  		if test.slash {
   854  			if found { // test needs a trailingSlash fix. It must not be found!
   855  				t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
   856  			}
   857  		} else {
   858  			if found != test.found || (found && (string(out) != test.out)) {
   859  				t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
   860  					test.in, string(out), found, test.out, test.found)
   861  				return
   862  			}
   863  		}
   864  	}
   865  }
   866  
   867  func TestTreeInvalidNodeType(t *testing.T) {
   868  	const panicMsg = "invalid node type"
   869  
   870  	tree := &node{}
   871  	tree.addRoute("/", fakeHandler("/"))
   872  	tree.addRoute("/:page", fakeHandler("/:page"))
   873  
   874  	// set invalid node type
   875  	tree.children[0].nType = 42
   876  
   877  	// normal lookup
   878  	recv := catchPanic(func() {
   879  		tree.getValue("/test", nil, getSkippedNodes(), false)
   880  	})
   881  	if rs, ok := recv.(string); !ok || rs != panicMsg {
   882  		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
   883  	}
   884  
   885  	// case-insensitive lookup
   886  	recv = catchPanic(func() {
   887  		tree.findCaseInsensitivePath("/test", true)
   888  	})
   889  	if rs, ok := recv.(string); !ok || rs != panicMsg {
   890  		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
   891  	}
   892  }
   893  
   894  func TestTreeInvalidParamsType(t *testing.T) {
   895  	tree := &node{}
   896  	tree.wildChild = true
   897  	tree.children = append(tree.children, &node{})
   898  	tree.children[0].nType = 2
   899  
   900  	// set invalid Params type
   901  	params := make(Params, 0)
   902  
   903  	// try to trigger slice bounds out of range with capacity 0
   904  	tree.getValue("/test", &params, getSkippedNodes(), false)
   905  }
   906  
   907  func TestTreeWildcardConflictEx(t *testing.T) {
   908  	conflicts := [...]struct {
   909  		route        string
   910  		segPath      string
   911  		existPath    string
   912  		existSegPath string
   913  	}{
   914  		{"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
   915  		{"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
   916  		{"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
   917  		{"/con:nection", ":nection", `/con:tact`, `:tact`},
   918  	}
   919  
   920  	for _, conflict := range conflicts {
   921  		// I have to re-create a 'tree', because the 'tree' will be
   922  		// in an inconsistent state when the loop recovers from the
   923  		// panic which threw by 'addRoute' function.
   924  		tree := &node{}
   925  		routes := [...]string{
   926  			"/con:tact",
   927  			"/who/are/*you",
   928  			"/who/foo/hello",
   929  		}
   930  
   931  		for _, route := range routes {
   932  			tree.addRoute(route, fakeHandler(route))
   933  		}
   934  
   935  		recv := catchPanic(func() {
   936  			tree.addRoute(conflict.route, fakeHandler(conflict.route))
   937  		})
   938  
   939  		if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'", conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) {
   940  			t.Fatalf("invalid wildcard conflict error (%v)", recv)
   941  		}
   942  	}
   943  }
   944  

View as plain text